Skip to content

Commit eff0fc8

Browse files
authored
AI Launchpad: first-pass design-feedback fixes (DSGCOM-678) (#50088)
* AI Launchpad: drop redundant write_3_posts task from the tailoring menu The model could pick both write_3_posts and first_post_published, which read as duplicates in the same list (DSGCOM-678). Remove write_3_posts from the prompt's TASK_MENU so it's no longer offered; the menu stays a subset of the catalog (AI_Launchpad_Task_Menu_Test still passes). * AI Launchpad: keep social task subtitles general, not network-specific Design feedback (DSGCOM-678): "Connect your social media accounts" read too specific (e.g. naming Instagram/Pinterest) when we do not know which platforms the user uses. Add a prompt rule so connect_social_media / drive_traffic / post_sharing_enabled subtitles stay about growing the audience and engaging visitors, without naming specific networks. * AI Launchpad: let wizard goal-card copy span the full width on mobile Design feedback (DSGCOM-678): the Educate and Portfolio descriptions did not extend across the container on mobile. `text-wrap: balance` evens line lengths, which keeps the copy narrow on the full-width single-column mobile cards. Scope balance to the two-column desktop grid and use `text-wrap: pretty` on mobile so longer copy fills the width while still avoiding orphans. * AI Launchpad: use WPDS state icons in the tailored list Design feedback (DSGCOM-678): use WPDS icons for the task states. Replace the inline hand-rolled SVGs with @wordpress/icons `border` (a dashed ring) for an active/not-started task and `published` (a check-in-circle) for a done one. Icons fill from currentColor so the existing grey toning still applies. * AI Launchpad: make the tailored list an accordion (one card open at a time) Design feedback (DSGCOM-678): only one task card should be open at a time, and opening a different card should collapse the active one. Replace the cards uncontrolled defaultOpen with parent-controlled open/onOpenChange: a single openId state means opening any card closes the rest, and the user can still collapse all. The first incomplete card auto-opens once (guarded by a ref so a fully-collapsed list is never force-reopened). firstIncompleteIndex becomes nextIncompleteId (id-based, null when all complete). * AI Launchpad: auto-expand the next task when one is skipped or completed Design feedback (DSGCOM-678): skipping a task should expand the next one. handleSkip now advances the accordion to the next incomplete task (via a new nextIncompleteId afterId overload that walks past the skipped id, falling back to any remaining incomplete task). Apply the same advance to handleMarkComplete so completing the open card does not leave the whole list collapsed. * AI Launchpad: add a site appearance-editor quick link to the site preview Design feedback (DSGCOM-678): keep the preview thumbnail as a quick link into the site editor. The REST site payload returns edit_url and SitePreview renders the thumbnail as a link with a hover "Edit site" overlay. Block themes point at the Site Editor (site-editor.php); classic themes, which cannot use it, fall back to the Customizer (customize.php) so the quick link is always available. * AI Launchpad: trim verbose comments and drop ticket references Comments-only cleanup across the feature: remove the internal ticket references from code comments, collapse multi-paragraph docblocks and decision-narration to terse one-line summaries, and delete inline comments that just restated the code. Lint-required docblock tags (@param/@return/@Package) are preserved; no code, logic, or behavior changes.
1 parent d2d9747 commit eff0fc8

36 files changed

Lines changed: 493 additions & 713 deletions
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: changed
3+
4+
AI Launchpad: address first-pass design feedback (DSGCOM-678) — a single-open accordion task list that auto-expands the next task on skip or completion, WPDS state icons, a Site Editor / Customizer quick link on the site preview, full-width goal copy on mobile, more general social-task subtitles, and removal of the redundant write-3-posts task.

projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/ai-launchpad.php

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
1111
use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills;
1212

13-
// helpers.php defines the shared option reader the listeners depend on, so it
14-
// loads first. The REST controller and completion listeners self-register on
15-
// their own hooks (rest_api_init / init) at load time, and must run on every
16-
// request regardless of which admin page is showing.
13+
// helpers.php defines the shared option reader the listeners depend on, so it loads first.
1714
require_once __DIR__ . '/helpers.php';
1815
require_once __DIR__ . '/eligibility.php';
1916
require_once __DIR__ . '/class-ai-launchpad-memberships.php';
@@ -31,10 +28,7 @@
3128
class AI_Launchpad {
3229

3330
/**
34-
* Admin page slug. The `-wp-admin` suffix is the wp-build convention for
35-
* pages that integrate with the standard wp-admin chrome: the generated
36-
* page PHP only enqueues its assets when `$_GET['page']` matches
37-
* `<page-id>-wp-admin`.
31+
* Admin page slug. The `-wp-admin` suffix is the wp-build convention for pages that integrate with wp-admin chrome.
3832
*/
3933
const MENU_SLUG = 'ai-launchpad-wp-admin';
4034

@@ -71,23 +65,15 @@ private static function is_ai_launchpad_request() {
7165
/**
7266
* Whether the current site is eligible for the AI Launchpad.
7367
*
74-
* MVP gate: paid plan, not already AI-onboarded, and explicitly enabled for
75-
* the site via the `wpcom_ai_launchpad_enabled` option (set per-site over
76-
* wp-cli). Replaces the earlier automattician/blog-sticker check, which did
77-
* not work on Atomic: `is_automattician()` is undefined there and blog
78-
* stickers require the wpcom sandbox. The option works identically on Simple
79-
* and Atomic and is context-independent (admin, REST, and CLI agree).
68+
* Gate: paid plan, not already AI-onboarded, and explicitly enabled for the site via the `wpcom_ai_launchpad_enabled` option.
8069
*
8170
* @return bool
8271
*/
8372
public static function is_eligible() {
8473
static $eligible = null;
8574

8675
if ( null === $eligible ) {
87-
// Cheapest gate first: the per-site option disqualifies the vast
88-
// majority of sites with a single option read, before the more
89-
// expensive purchases lookup in has_paid_plan() runs on every admin
90-
// page. Memoized since the result is stable for the request.
76+
// Cheapest gate first: the per-site option disqualifies most sites before the more expensive purchases lookup.
9177
$eligible = self::is_enabled_for_site()
9278
&& ! self::was_ai_onboarded()
9379
&& self::has_paid_plan();
@@ -139,9 +125,7 @@ public static function register_menu() {
139125
return;
140126
}
141127

142-
// The render callback only exists when build/build.php has been loaded,
143-
// which happens on the AI Launchpad page itself. On other admin screens
144-
// the menu just needs a registered slug.
128+
// The render callback only exists once build/build.php is loaded, which happens on the AI Launchpad page itself.
145129
$callback = function_exists( self::RENDER_CALLBACK )
146130
? self::RENDER_CALLBACK
147131
: '__return_empty_string';
@@ -205,15 +189,11 @@ public static function enqueue_jwt_initial_state() {
205189
/**
206190
* Fix import map ordering for the wp-build boot script.
207191
*
208-
* In wp-admin, _wp_footer_scripts (classic scripts) and print_import_map
209-
* both hook into admin_print_footer_scripts at priority 10, but
210-
* _wp_footer_scripts is registered first. This causes the inline
211-
* import("@wordpress/boot") to execute before the import map exists.
192+
* In wp-admin both _wp_footer_scripts and print_import_map hook admin_print_footer_scripts at priority 10, but
193+
* _wp_footer_scripts runs first, so the inline import("@wordpress/boot") executes before the import map exists.
194+
* This moves the import() call to a <script type="module"> printed at priority 20, after the import map.
212195
*
213-
* This fix moves the import() call from the classic inline script to a
214-
* <script type="module"> printed at priority 20 (after the import map).
215-
*
216-
* @todo Remove once @wordpress/build ships with the loader.js fix upstream
196+
* @todo Remove once @wordpress/build ships the loader.js fix upstream
217197
* (WordPress/gutenberg#76870) and Jetpack updates the dependency.
218198
*/
219199
private static function fix_boot_import_map_ordering() {
@@ -227,7 +207,6 @@ static function () use ( $handle ) {
227207
return;
228208
}
229209

230-
// Find and extract the import("@wordpress/boot") inline script.
231210
$boot_script = null;
232211
$remaining = array();
233212
foreach ( $data as $line ) {
@@ -242,7 +221,6 @@ static function () use ( $handle ) {
242221
return;
243222
}
244223

245-
// Remove from the classic script handle.
246224
wp_scripts()->add_data( $handle, 'after', $remaining );
247225

248226
// Re-emit as a module script after the import map.

projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-about-page-listener.php

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,11 @@
66
*/
77

88
/**
9-
* Completes the About-page tasks from wp-admin when the AI Launchpad selected
10-
* them: `add_about_page` (the AI-created About page is first published) and
11-
* `update_about_page` (it is edited again afterwards).
9+
* Completes the AI-selected About-page tasks from wp-admin: `add_about_page` (first published) and
10+
* `update_about_page` (edited again afterwards).
1211
*
13-
* The catalog's own completion for these depends on the `_wpcom_template_layout_category`
14-
* meta, which is provided by the dotcom editor toolkit and is not registered on
15-
* Atomic, and on the AI's `createPatternPage` only ever creating a draft. So this
16-
* tags the AI-created About page with its own marker meta and watches that page's
17-
* publish/update transitions instead — independent of the layout-category meta.
12+
* The catalog's own completion depends on the `_wpcom_template_layout_category` meta, which is not registered on
13+
* Atomic, so this tags the AI-created About page with its own marker meta and watches that page's transitions instead.
1814
*/
1915
class AI_Launchpad_About_Page_Listener {
2016

@@ -34,9 +30,9 @@ public static function register() {
3430
}
3531

3632
/**
37-
* Registers the marker meta so the block editor preserves it and the create
38-
* request can set it. Protected (underscore-prefixed), so the auth callback
39-
* limits writes to users who can edit pages.
33+
* Registers the marker meta so the block editor preserves it and the create request can set it.
34+
*
35+
* The auth callback limits writes to users who can edit pages.
4036
*
4137
* @return void
4238
*/
@@ -56,9 +52,8 @@ public static function register_meta() {
5652
}
5753

5854
/**
59-
* Completes the About-page tasks on the AI About page's status transitions:
60-
* first publish -> add_about_page, a later edit of the published page ->
61-
* update_about_page. Only fires for the marked page and AI-selected tasks.
55+
* Completes the About-page tasks on the marked page's status transitions: first publish -> add_about_page,
56+
* a later edit of the published page -> update_about_page.
6257
*
6358
* @param string $new_status The new post status.
6459
* @param string $old_status The previous post status.
@@ -80,12 +75,10 @@ public static function maybe_complete( $new_status, $old_status, $post ) {
8075
}
8176

8277
if ( 'publish' !== $old_status ) {
83-
// First publish of the AI About page.
8478
if ( in_array( 'add_about_page', $ai_task_ids, true ) ) {
8579
wpcom_mark_launchpad_task_complete( 'add_about_page' );
8680
}
8781
} elseif ( in_array( 'update_about_page', $ai_task_ids, true ) ) {
88-
// A later edit of the already-published AI About page.
8982
wpcom_mark_launchpad_task_complete( 'update_about_page' );
9083
}
9184
}

projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-dev-enable.php

Lines changed: 19 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,20 @@
22
/**
33
* AI Launchpad no-CLI test-enable handler.
44
*
5-
* Lets a tester turn the AI Launchpad on (and reset its state) for a site
6-
* straight from the browser, removing the `wp option update
7-
* wpcom_ai_launchpad_enabled 1` / reset-script SSH steps that testing otherwise
8-
* requires. Modeled on the existing wpcom query-param overrides
9-
* (wpcom-dashboard-redesign-override.php `?enable-dashboard-redesign=1`,
10-
* jetpack stats `?enable_new_stats=1`), which likewise flip a feature straight
11-
* from a `$_GET` flag.
5+
* Lets a tester turn the AI Launchpad on (and reset its state) for a site straight from the browser.
126
*
137
* Recognized query args (on any admin page, for a `manage_options` user):
148
* ?enable-ai-launchpad=1 Set wpcom_ai_launchpad_enabled to 1.
159
* ?enable-ai-launchpad=0 Delete wpcom_ai_launchpad_enabled (turn back off).
16-
* ?reset-ai-launchpad=1 Clear the wizard / AI-output / dismissed / task-status
17-
* options so the wizard runs fresh.
10+
* ?reset-ai-launchpad=1 Clear the wizard / AI-output / dismissed / task-status options so the wizard runs fresh.
1811
*
19-
* Each action redirects to the clean AI Launchpad page URL so a refresh does not
20-
* re-fire it.
12+
* Hooked on `admin_menu`, not `admin_init`: when the feature is OFF its page is unregistered and
13+
* `user_can_access_admin_page()` dies before `admin_init`; `admin_menu` fires before that check, so the page's own
14+
* URL can self-enable instead of dying first.
2115
*
22-
* Hooked on `admin_menu`, not `admin_init`: when the feature is OFF its admin
23-
* page is unregistered, and WordPress runs the `user_can_access_admin_page()`
24-
* check (and dies with "you are not allowed to access this page") in
25-
* wp-admin/menu.php — which loads *before* `admin_init`. `admin_menu` fires
26-
* inside that same file but before the access check, so handling it there lets
27-
* the launchpad page's own URL self-enable instead of dying first.
28-
*
29-
* Gate: `current_user_can( 'manage_options' )` only — no nonce, matching the
30-
* wpcom precedents, so the URL stays bookmarkable/shareable. NOTE: this ships to
31-
* production on real sites, where it lets any paid-site admin self-enable the
32-
* (otherwise OFF) feature on their own site. That exposure was reviewed and
33-
* accepted as an interim testing affordance; tighten the gate before the
34-
* controlled rollout (DOTOBRD-456) if the feature must stay invisible to
35-
* customers.
16+
* Gate: `current_user_can( 'manage_options' )` only — no nonce, so the URL stays bookmarkable. This ships to
17+
* production, where it lets any paid-site admin self-enable the (otherwise OFF) feature on their own site; tighten
18+
* the gate before the controlled rollout if the feature must stay invisible to customers.
3619
*
3720
* @package automattic/jetpack-mu-wpcom
3821
*/
@@ -48,9 +31,9 @@ class AI_Launchpad_Dev_Enable {
4831
const OPTION_ENABLED = 'wpcom_ai_launchpad_enabled';
4932

5033
/**
51-
* Options cleared by a reset, matching docs/bin/reset-ai-launchpad-test-site.sh.
52-
* The first three reference the REST controller's canonical constants so a
53-
* rename there can't silently leave the reset clearing a stale option name.
34+
* Options cleared by a reset.
35+
*
36+
* The first three reference the REST controller's constants so a rename there can't leave a stale option name here.
5437
*/
5538
const RESET_OPTIONS = array(
5639
AI_Launchpad_REST::OPTION_WIZARD,
@@ -60,9 +43,7 @@ class AI_Launchpad_Dev_Enable {
6043
);
6144

6245
/**
63-
* Redirect targets returned by handle(): nothing to do, the AI Launchpad page,
64-
* or the wp-admin dashboard. Kept as abstract tokens (not URLs) so handle() can
65-
* be unit-tested without loading the AI Launchpad page bootstrap.
46+
* Redirect targets returned by handle(), kept as abstract tokens (not URLs) so handle() can be unit-tested.
6647
*/
6748
const REDIRECT_NONE = '';
6849
const REDIRECT_PAGE = 'page';
@@ -78,10 +59,9 @@ public static function register() {
7859
}
7960

8061
/**
81-
* Acts on the test-enable / reset query params, then redirects so a refresh
82-
* does not re-fire the action. Disabling lands on the dashboard (the gated
83-
* page is gone); everything else lands on the AI Launchpad page, where the
84-
* fresh request re-registers the now-eligible menu.
62+
* Acts on the test-enable / reset query params, then redirects so a refresh does not re-fire the action.
63+
*
64+
* Disabling lands on the dashboard (the gated page is gone); everything else lands on the AI Launchpad page.
8565
*
8666
* @return void
8767
*/
@@ -101,11 +81,9 @@ public static function maybe_handle_request() {
10181
}
10282

10383
/**
104-
* Applies the requested option changes and returns where to send the user, as
105-
* one of the REDIRECT_* tokens. Split from the redirect/exit — and kept free of
106-
* the AI Launchpad page bootstrap — so it can be unit-tested in isolation. No-op
107-
* (and cheap) on the overwhelming majority of admin requests, which carry
108-
* neither param.
84+
* Applies the requested option changes and returns where to send the user, as one of the REDIRECT_* tokens.
85+
*
86+
* Split from the redirect/exit so it can be unit-tested in isolation.
10987
*
11088
* @return string One of the REDIRECT_* constants (REDIRECT_NONE when there is
11189
* nothing to do: no recognized param, or no capability).
@@ -142,11 +120,7 @@ public static function handle() {
142120
}
143121
// phpcs:enable WordPress.Security.NonceVerification.Recommended
144122

145-
// Disabling removes the (eligibility-gated) launchpad page, so land on the
146-
// dashboard rather than the now-inaccessible page. Otherwise go to the
147-
// launchpad: the redirect starts a fresh request where the menu
148-
// re-registers. (On a site without a paid plan the page stays gated, but
149-
// that is out of scope — the feature requires a paid plan.)
123+
// Disabling removes the gated launchpad page, so land on the dashboard rather than the now-inaccessible page.
150124
return $disabling ? self::REDIRECT_DASHBOARD : self::REDIRECT_PAGE;
151125
}
152126
}

projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-listeners.php

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
*/
77

88
/**
9-
* Registers the existing Launchpad completion listeners for the tasks the AI
10-
* Launchpad selected, mirroring Launchpad_Task_Lists::add_hooks_for_active_tasks()
11-
* without the fullscreen-launchpad gate. Task selection is read from the
12-
* `wpcom_ai_launchpad_ai_output` option; completion writes go through each
13-
* task's own `add_listener_callback` into `launchpad_checklist_tasks_statuses`.
9+
* Registers the existing Launchpad completion listeners for the AI-selected tasks, without the fullscreen gate.
10+
*
11+
* Task selection is read from `wpcom_ai_launchpad_ai_output`; completion writes go through each task's own
12+
* `add_listener_callback` into `launchpad_checklist_tasks_statuses`.
1413
*/
1514
class AI_Launchpad_Listeners {
1615

@@ -25,9 +24,7 @@ public static function register() {
2524
}
2625

2726
/**
28-
* Treats AI-selected tasks as active so their shared completion callbacks
29-
* write status even when the task is absent from the site's `site_intent`
30-
* task list.
27+
* Treats AI-selected tasks as active so their completion callbacks write status even when absent from `site_intent`.
3128
*
3229
* @param bool $is_active Whether the task is active per the site_intent task list.
3330
* @param string $task_id The task being completed.

projects/packages/jetpack-mu-wpcom/src/features/ai-launchpad/class-ai-launchpad-memberships.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,10 @@
88
*/
99

1010
/**
11-
* Recomputes the memberships task completion from Jetpack_Memberships' local
12-
* signals, which the AI Launchpad REST read path uses instead of the catalog's
13-
* own `is_complete_callback`s for these tasks.
11+
* Recomputes the memberships task completion from Jetpack_Memberships' local signals.
1412
*
15-
* The catalog computes these from `wpcom_launchpad_get_membership_settings()`,
16-
* which returns null under `IS_ATOMIC`, so `wpcom_launchpad_is_stripe_connected`
17-
* / `wpcom_launchpad_has_paid_membership_plans` are always false on Atomic — and
18-
* because those callbacks recompute (ignoring any stored option), an
19-
* option-writing listener could not surface them. The real state is readable
20-
* locally on Atomic, though: Jetpack_Memberships syncs the connected-account
21-
* flag down as a site option and mirrors membership plans as the local
22-
* `jp_mem_plan` CPT. This reads those instead.
13+
* The catalog's own callbacks are always false on Atomic (their membership settings return null there), so the REST
14+
* read path uses this instead. Jetpack_Memberships syncs the connected-account flag and membership plans down locally.
2315
*/
2416
class AI_Launchpad_Memberships {
2517

@@ -59,8 +51,7 @@ public static function is_task_complete( $task_id ) {
5951
switch ( $task_id ) {
6052
case 'stripe_connected':
6153
case 'set_up_payments':
62-
// Stripe connected = a payment method is set up; wpcom completes both
63-
// on Stripe-connect (memberships/connected-accounts.php).
54+
// A connected account means a payment method is set up; wpcom completes both on Stripe-connect.
6455
return (bool) Jetpack_Memberships::has_connected_account();
6556
case 'paid_offer_created':
6657
return (bool) Jetpack_Memberships::has_configured_plans_jetpack_recurring_payments();

0 commit comments

Comments
 (0)