Commit 1fba7d2ef7e for woocommerce

commit 1fba7d2ef7eeda58c4cd376ec9d8084a6cb9756e
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Mon Apr 20 10:32:35 2026 +0200

    Fix command palette registration across wp-admin screens (#64164)

    * Fix command palette assets loading on wp-admin pages

    * update baseline

    * lint code

    * replace self with this

    * fix: convert command-palette-analytics to domReady and remove origin tracking

    Agent-Logs-Url: https://github.com/woocommerce/woocommerce/sessions/14bbc27a-9a0b-48cf-b322-46d4eb573aea

    Co-authored-by: gigitux <4463174+gigitux@users.noreply.github.com>

    * update comment

    * update e2e test

    * lint code

    * remove settings registration

    * fix flaky test

    * lint code

    * fix e2e test

    * fix logic for WordPress 6.8

    ---------

    Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
    Co-authored-by: gigitux <4463174+gigitux@users.noreply.github.com>

diff --git a/packages/js/email-editor/src/components/block-editor/editor.tsx b/packages/js/email-editor/src/components/block-editor/editor.tsx
index 22c1f8bd541..d4eadae6e5f 100644
--- a/packages/js/email-editor/src/components/block-editor/editor.tsx
+++ b/packages/js/email-editor/src/components/block-editor/editor.tsx
@@ -163,15 +163,19 @@ export function InnerEditor( {
 			</div>
 		);
 	}
+	// In WordPress 6.8 WooCommerce commands are registered because Core does
+	// not mount the global CommandMenu. Use that as a signal to render our own
+	// CommandMenu fallback. Core loads it starting in WordPress 6.9.
+	const isWordPress68 = allCommands.every( ( { name } ) =>
+		name.includes( 'woocommerce' )
+	);

 	recordEventOnce( 'editor_layout_loaded' );
 	return (
 		<SlotFillProvider>
 			<ErrorBoundary canCopyContent>
-				{ /* The CommandMenu is not needed if the commands are registered. The CommandMenu can be removed after we drop support for WP 6.8. */ }
-				{ ( ! allCommands || allCommands.length === 0 ) && (
-					<CommandMenu />
-				) }
+				{ /* Keep this fallback only for WordPress 6.8. Core mounts the CommandMenu in 6.9+. */ }
+				{ isWordPress68 && <CommandMenu /> }
 				<Editor
 					postId={ currentPost.postId }
 					postType={ currentPost.postType }
diff --git a/plugins/woocommerce/changelog/fix-command-palette-admin-enqueue b/plugins/woocommerce/changelog/fix-command-palette-admin-enqueue
new file mode 100644
index 00000000000..c16200106d3
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-command-palette-admin-enqueue
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix command palette command registration on WooCommerce admin pages by loading the command scripts in admin and preserving settings/analytics command hydration.
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js
index 90d01a74aaf..98c1ffc0725 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette-analytics/index.js
@@ -3,17 +3,15 @@
  */
 import { __, sprintf } from '@wordpress/i18n';
 import { chartBar } from '@wordpress/icons';
-import { useEffect } from '@wordpress/element';
-import { registerPlugin } from '@wordpress/plugins';
+import domReady from '@wordpress/dom-ready';
 import { addQueryArgs } from '@wordpress/url';
-import { useSelect } from '@wordpress/data';

 /**
  * Internal dependencies
  */
 import { registerCommandWithTracking } from '../command-palette/register-command-with-tracking';

-const registerWooCommerceAnalyticsCommand = ( { label, path, origin } ) => {
+const registerWooCommerceAnalyticsCommand = ( { label, path } ) => {
 	registerCommandWithTracking( {
 		name: `woocommerce${ path }`,
 		label: sprintf(
@@ -28,40 +26,22 @@ const registerWooCommerceAnalyticsCommand = ( { label, path, origin } ) => {
 				path,
 			} );
 		},
-		origin,
 	} );
 };

-const WooCommerceAnalyticsCommands = () => {
-	const { editedPostType } = useSelect( ( select ) => {
-		const editor = select( 'core/editor' );
-		return {
-			editedPostType: editor?.getCurrentPostType?.() ?? null,
-		};
-	} );
-	const origin = editedPostType ? editedPostType + '-editor' : null;
-
-	useEffect( () => {
-		if (
-			window.hasOwnProperty( 'wcCommandPaletteAnalytics' ) &&
-			window.wcCommandPaletteAnalytics.hasOwnProperty( 'reports' ) &&
-			Array.isArray( window.wcCommandPaletteAnalytics.reports )
-		) {
-			const analyticsReports = window.wcCommandPaletteAnalytics.reports;
+domReady( () => {
+	if (
+		window.hasOwnProperty( 'wcCommandPaletteAnalytics' ) &&
+		window.wcCommandPaletteAnalytics.hasOwnProperty( 'reports' ) &&
+		Array.isArray( window.wcCommandPaletteAnalytics.reports )
+	) {
+		const analyticsReports = window.wcCommandPaletteAnalytics.reports;

-			analyticsReports.forEach( ( analyticsReport ) => {
-				registerWooCommerceAnalyticsCommand( {
-					label: analyticsReport.title,
-					path: analyticsReport.path,
-					origin,
-				} );
+		analyticsReports.forEach( ( analyticsReport ) => {
+			registerWooCommerceAnalyticsCommand( {
+				label: analyticsReport.title,
+				path: analyticsReport.path,
 			} );
-		}
-	}, [ origin ] );
-
-	return null;
-};
-
-registerPlugin( 'woocommerce-analytics-commands-registration', {
-	render: WooCommerceAnalyticsCommands,
+		} );
+	}
 } );
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette/index.js b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette/index.js
index a093fd818c1..8264c42d4c1 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette/index.js
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/command-palette/index.js
@@ -1,51 +1,25 @@
 /**
  * External dependencies
  */
-import { registerPlugin } from '@wordpress/plugins';
-import { __, sprintf } from '@wordpress/i18n';
-import { box, plus, settings } from '@wordpress/icons';
-import { useEffect, useMemo, useRef } from '@wordpress/element';
-import { dispatch, useSelect } from '@wordpress/data';
-import { store as coreStore } from '@wordpress/core-data';
-import { addQueryArgs } from '@wordpress/url';
-import { recordEvent, queueRecordEvent } from '@woocommerce/tracks';
+import { queueRecordEvent, recordEvent } from '@woocommerce/tracks';
 import { store as commandsStore } from '@wordpress/commands';
+import { store as coreStore } from '@wordpress/core-data';
+import { dispatch, useSelect } from '@wordpress/data';
+import domReady from '@wordpress/dom-ready';
+import { useEffect, useMemo, useRef } from '@wordpress/element';
 import { decodeEntities } from '@wordpress/html-entities';
+import { __ } from '@wordpress/i18n';
+import { box, plus } from '@wordpress/icons';
+import { addQueryArgs } from '@wordpress/url';

 /**
  * Internal dependencies
  */
 import { registerCommandWithTracking } from './register-command-with-tracking';

-const registerWooCommerceSettingsCommand = ( { label, tab, origin } ) => {
-	registerCommandWithTracking( {
-		name: `woocommerce/settings-${ tab }`,
-		label: sprintf(
-			// translators: %s is the title of the Settings Tab. This is used as a command in the Command Palette.
-			__( 'WooCommerce Settings: %s', 'woocommerce' ),
-			label
-		),
-		icon: settings,
-		callback: () => {
-			document.location = addQueryArgs( 'admin.php', {
-				page: 'wc-settings',
-				tab,
-			} );
-		},
-		origin,
-	} );
-};
-
 // Code adapted from the equivalent in Gutenberg:
 // https://github.com/WordPress/gutenberg/blob/8863b49b7e686f555e8b8adf70cc588c4feebfbf/packages/core-commands/src/site-editor-navigation-commands.js#L36C7-L36C44
 function useProductCommandLoader( { search } ) {
-	const { editedPostType } = useSelect( ( select ) => {
-		const editor = select( 'core/editor' );
-		return {
-			editedPostType: editor?.getCurrentPostType?.() ?? null,
-		};
-	} );
-	const origin = editedPostType ? editedPostType + '-editor' : null;
 	// Track searched values. We add a 300 ms delay to avoid tracking while typing.
 	const trackingSearchTimeout = useRef( null );
 	useEffect( () => {
@@ -54,14 +28,13 @@ function useProductCommandLoader( { search } ) {
 			trackingSearchTimeout.current = setTimeout( () => {
 				recordEvent( 'woocommerce_command_palette_search', {
 					value: search,
-					origin,
 				} );
 			}, 300 );
 		}
 		return () => {
 			clearTimeout( trackingSearchTimeout.current );
 		};
-	}, [ search, origin ] );
+	}, [ search ] );

 	const postType = 'product';
 	const { records, isLoading } = useSelect(
@@ -99,7 +72,6 @@ function useProductCommandLoader( { search } ) {
 				callback: ( { close } ) => {
 					queueRecordEvent( 'woocommerce_command_palette_submit', {
 						name: 'woocommerce/product',
-						origin,
 					} );

 					const args = {
@@ -112,7 +84,7 @@ function useProductCommandLoader( { search } ) {
 				},
 			};
 		} );
-	}, [ records, origin ] );
+	}, [ records ] );

 	return {
 		commands,
@@ -120,103 +92,50 @@ function useProductCommandLoader( { search } ) {
 	};
 }

-const WooCommerceCommands = () => {
-	const { editedPostType } = useSelect( ( select ) => {
-		const editor = select( 'core/editor' );
-		return {
-			editedPostType: editor?.getCurrentPostType?.() ?? null,
-		};
+domReady( () => {
+	registerCommandWithTracking( {
+		name: 'woocommerce/add-new-product',
+		label: __( 'Add new product', 'woocommerce' ),
+		icon: plus,
+		callback: () => {
+			document.location = addQueryArgs( 'post-new.php', {
+				post_type: 'product',
+			} );
+		},
 	} );
-	const origin = editedPostType ? editedPostType + '-editor' : null;
-	const { isCommandPaletteOpen } = useSelect( ( select ) => {
-		const { isOpen } = select( commandsStore );
-		return {
-			isCommandPaletteOpen: isOpen(),
-		};
-	}, [] );
-	const wasCommandPaletteOpen = useRef( false );
-
-	useEffect( () => {
-		if ( isCommandPaletteOpen && ! wasCommandPaletteOpen.current ) {
-			recordEvent( 'woocommerce_command_palette_open', {
-				origin,
+	registerCommandWithTracking( {
+		name: 'woocommerce/add-new-order',
+		label: __( 'Add new order', 'woocommerce' ),
+		icon: plus,
+		callback: () => {
+			document.location = addQueryArgs( 'admin.php', {
+				page: 'wc-orders',
+				action: 'new',
 			} );
-		}
-		wasCommandPaletteOpen.current = isCommandPaletteOpen;
-	}, [ isCommandPaletteOpen, origin ] );
-
-	useEffect( () => {
-		registerCommandWithTracking( {
-			name: 'woocommerce/add-new-product',
-			label: __( 'Add new product', 'woocommerce' ),
-			icon: plus,
-			callback: () => {
-				document.location = addQueryArgs( 'post-new.php', {
-					post_type: 'product',
-				} );
-			},
-			origin,
-		} );
-		registerCommandWithTracking( {
-			name: 'woocommerce/add-new-order',
-			label: __( 'Add new order', 'woocommerce' ),
-			icon: plus,
-			callback: () => {
-				document.location = addQueryArgs( 'admin.php', {
-					page: 'wc-orders',
-					action: 'new',
-				} );
-			},
-			origin,
-		} );
-		registerCommandWithTracking( {
-			name: 'woocommerce/view-products',
-			label: __( 'Products', 'woocommerce' ),
-			icon: box,
-			callback: () => {
-				document.location = addQueryArgs( 'edit.php', {
-					post_type: 'product',
-				} );
-			},
-			origin,
-		} );
-		registerCommandWithTracking( {
-			name: 'woocommerce/view-orders',
-			label: __( 'Orders', 'woocommerce' ),
-			icon: box,
-			callback: () => {
-				document.location = addQueryArgs( 'admin.php', {
-					page: 'wc-orders',
-				} );
-			},
-			origin,
-		} );
-		dispatch( commandsStore ).registerCommandLoader( {
-			name: 'woocommerce/product',
-			hook: useProductCommandLoader,
-		} );
-
-		if (
-			window.hasOwnProperty( 'wcCommandPaletteSettings' ) &&
-			window.wcCommandPaletteSettings.hasOwnProperty( 'settingsTabs' ) &&
-			Array.isArray( window.wcCommandPaletteSettings.settingsTabs )
-		) {
-			const settingsCommands =
-				window.wcCommandPaletteSettings.settingsTabs;
-
-			settingsCommands.forEach( ( settingsCommand ) => {
-				registerWooCommerceSettingsCommand( {
-					label: settingsCommand.label,
-					tab: settingsCommand.key,
-					origin,
-				} );
+		},
+	} );
+	registerCommandWithTracking( {
+		name: 'woocommerce/view-products',
+		label: __( 'Products', 'woocommerce' ),
+		icon: box,
+		callback: () => {
+			document.location = addQueryArgs( 'edit.php', {
+				post_type: 'product',
 			} );
-		}
-	}, [ origin ] );
-
-	return null;
-};
-
-registerPlugin( 'woocommerce-commands-registration', {
-	render: WooCommerceCommands,
+		},
+	} );
+	registerCommandWithTracking( {
+		name: 'woocommerce/view-orders',
+		label: __( 'Orders', 'woocommerce' ),
+		icon: box,
+		callback: () => {
+			document.location = addQueryArgs( 'admin.php', {
+				page: 'wc-orders',
+			} );
+		},
+	} );
+	dispatch( commandsStore ).registerCommandLoader( {
+		name: 'woocommerce/product',
+		hook: useProductCommandLoader,
+	} );
 } );
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
index 858acfb0a06..24c8f462208 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php
@@ -37,7 +37,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
 			add_action( 'admin_init', array( $this, 'register_scripts' ) );
 			add_action( 'admin_enqueue_scripts', array( $this, 'admin_styles' ) );
 			add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
-			add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
+			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_command_palette_assets' ) );
 		}

 		/**
@@ -813,12 +813,12 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
 		}

 		/**
-		 * Enqueue a script in the block editor.
+		 * Enqueue a script in WordPress admin.
 		 * Similar to `WCAdminAssets::register_script()` but without enqueuing unnecessary dependencies.
 		 *
 		 * @return void
 		 */
-		private function enqueue_block_editor_script( $script_path_name, $script_name ) {
+		private function enqueue_script( string $script_path_name, string $script_name ) {
 			$script_assets_filename = WCAdminAssets::get_script_asset_filename( $script_path_name, $script_name );
 			$script_assets          = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER .  $script_path_name . '/' . $script_assets_filename;

@@ -832,36 +832,12 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
 		}

 		/**
-		 * Enqueue block editor assets.
+		 * Enqueue command palette assets.
 		 *
 		 * @return void
 		 */
-		public function enqueue_block_editor_assets() {
-			$settings_tabs = apply_filters('woocommerce_settings_tabs_array', []);
-
-			if ( is_array( $settings_tabs ) && count( $settings_tabs ) > 0  ) {
-				$formatted_settings_tabs = array();
-				foreach ($settings_tabs as $key => $label) {
-					if (
-						is_string( $key ) && $key !== "" &&
-						is_string( $label ) && $label !== ""
-					) {
-						$formatted_settings_tabs[] = array(
-							'key'   => $key,
-							'label' => wp_strip_all_tags( $label ),
-						);
-					}
-				}
-
-				self::enqueue_block_editor_script( 'wp-admin-scripts', 'command-palette' );
-				wp_localize_script(
-					'wc-admin-command-palette',
-					'wcCommandPaletteSettings',
-					array(
-						'settingsTabs'    => $formatted_settings_tabs,
-					)
-				);
-			}
+		public function enqueue_command_palette_assets() {
+			$this->enqueue_script( 'wp-admin-scripts', 'command-palette' );

 			$admin_features_disabled = apply_filters( 'woocommerce_admin_disabled', false );
 			if ( ! $admin_features_disabled ) {
@@ -886,12 +862,12 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) :
 					}, $analytics_reports );
 					$formatted_analytics_reports = array_filter( $formatted_analytics_reports, 'is_array' );

-					self::enqueue_block_editor_script( 'wp-admin-scripts', 'command-palette-analytics' );
+					$this->enqueue_script( 'wp-admin-scripts', 'command-palette-analytics' );
 					wp_localize_script(
 						'wc-admin-command-palette-analytics',
 						'wcCommandPaletteAnalytics',
 						array(
-							'reports'    => $formatted_analytics_reports,
+							'reports' => $formatted_analytics_reports,
 						)
 					);
 				}
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index aab8436e6d3..e095f7441bc 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -1500,18 +1500,6 @@ parameters:
 			count: 1
 			path: includes/admin/class-wc-admin-assets.php

-		-
-			message: '#^Method WC_Admin_Assets\:\:enqueue_block_editor_script\(\) has parameter \$script_name with no type specified\.$#'
-			identifier: missingType.parameter
-			count: 1
-			path: includes/admin/class-wc-admin-assets.php
-
-		-
-			message: '#^Method WC_Admin_Assets\:\:enqueue_block_editor_script\(\) has parameter \$script_path_name with no type specified\.$#'
-			identifier: missingType.parameter
-			count: 1
-			path: includes/admin/class-wc-admin-assets.php
-
 		-
 			message: '#^Method WC_Admin_Assets\:\:register_scripts\(\) has no return type specified\.$#'
 			identifier: missingType.return
diff --git a/plugins/woocommerce/tests/e2e-pw/tests/editor/command-palette.spec.ts b/plugins/woocommerce/tests/e2e-pw/tests/editor/command-palette.spec.ts
index 7e3c8da6cf8..a0bb3a271dc 100644
--- a/plugins/woocommerce/tests/e2e-pw/tests/editor/command-palette.spec.ts
+++ b/plugins/woocommerce/tests/e2e-pw/tests/editor/command-palette.spec.ts
@@ -2,16 +2,14 @@
  * External dependencies
  */
 import { Page } from '@playwright/test';
-import {
-	disableWelcomeModal,
-	WC_API_PATH,
-} from '@woocommerce/e2e-utils-playwright';
+import { WC_API_PATH } from '@woocommerce/e2e-utils-playwright';

 /**
  * Internal dependencies
  */
 import { ADMIN_STATE_PATH } from '../../playwright.config';
 import { expect, test as baseTest } from '../../fixtures/fixtures';
+import { getInstalledWordPressVersion } from '../../utils/wordpress';

 // need to figure out whether tests are being run on a mac
 const macOS = process.platform === 'darwin';
@@ -66,8 +64,22 @@ const test = baseTest.extend( {
 		} );
 	},
 	page: async ( { page }, use ) => {
-		await page.goto( 'wp-admin/post-new.php' );
-		await disableWelcomeModal( { page } );
+		const adminEntryPoint =
+			( await getInstalledWordPressVersion() ) === 6.8
+				? 'wp-admin/post-new.php'
+				: 'wp-admin';
+		const waitForCommandPalette = page.waitForResponse( ( response ) => {
+			return (
+				response
+					.url()
+					.includes( '/wp-admin-scripts/command-palette' ) &&
+				response.status() === 200
+			);
+		} );
+		await Promise.all( [
+			page.goto( adminEntryPoint ),
+			waitForCommandPalette,
+		] );
 		await use( page );
 	},
 } );
@@ -132,16 +144,6 @@ test( 'can use the product search command', async ( { page, product } ) => {
 	);
 } );

-test( 'can use a settings command', async ( { page } ) => {
-	await clickOnCommandPaletteOption( {
-		page,
-		optionName: 'WooCommerce Settings: Products',
-	} );
-
-	// Verify that the page has loaded.
-	await expect( page.getByText( 'Shop pages' ) ).toBeVisible();
-} );
-
 test( 'can use an analytics command', async ( { page } ) => {
 	await clickOnCommandPaletteOption( {
 		page,