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,