diff --git a/projects/plugins/jetpack/changelog/hide-legacy-ai-toolbar-with-sidebar b/projects/plugins/jetpack/changelog/hide-legacy-ai-toolbar-with-sidebar
new file mode 100644
index 000000000000..01074d49ea25
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/hide-legacy-ai-toolbar-with-sidebar
@@ -0,0 +1,4 @@
+Significance: patch
+Type: bugfix
+
+AI Assistant: Hide legacy block toolbar controls when Jetpack AI Sidebar content editing is enabled.
diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/image/with-ai-image-extension.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/image/with-ai-image-extension.tsx
index 06605b84aa7c..53b8aa3ef10f 100644
--- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/image/with-ai-image-extension.tsx
+++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/image/with-ai-image-extension.tsx
@@ -21,7 +21,10 @@ import debugFactory from 'debug';
import { store as seoStore } from '../../../../plugins/ai-assistant-plugin/components/seo-enhancer/store';
import useBlockModuleStatus from '../../hooks/use-block-module-status';
import { getFeatureAvailability } from '../../lib/utils/get-feature-availability';
-import { canAIAssistantBeEnabled } from '../lib/can-ai-assistant-be-enabled';
+import {
+ canAIAssistantBeEnabled,
+ isAiSidebarToolbarButtonEnabled,
+} from '../lib/can-ai-assistant-be-enabled';
import { preprocessImageContent } from '../lib/preprocess-image-content';
import { TYPE_ALT_TEXT, TYPE_CAPTION } from '../types';
import AiAssistantImageExtensionToolbarDropdown from './components/image-toolbar-dropdown';
@@ -231,16 +234,18 @@ const blockEditWithAiComponents = createHigherOrderComponent( BlockEdit => {
return (
<>
-
- request( TYPE_ALT_TEXT ) }
- onRequestCaption={ () => request( TYPE_CAPTION ) }
- loadingAltText={ loadingAltText }
- loadingCaption={ loadingCaption }
- disabled={ ! hasImage }
- wrapperRef={ wrapperRef }
- />
-
+ { ! isAiSidebarToolbarButtonEnabled && (
+
+ request( TYPE_ALT_TEXT ) }
+ onRequestCaption={ () => request( TYPE_CAPTION ) }
+ loadingAltText={ loadingAltText }
+ loadingCaption={ loadingCaption }
+ disabled={ ! hasImage }
+ wrapperRef={ wrapperRef }
+ />
+
+ ) }
>
);
}
diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/lib/can-ai-assistant-be-enabled.ts b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/lib/can-ai-assistant-be-enabled.ts
index 2c2d2ec72628..effd03a11041 100644
--- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/lib/can-ai-assistant-be-enabled.ts
+++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/lib/can-ai-assistant-be-enabled.ts
@@ -10,9 +10,11 @@ import { select } from '@wordpress/data';
import { getFeatureAvailability } from '../../lib/utils/get-feature-availability';
export const AI_ASSISTANT_SUPPORT_NAME = 'ai-assistant-support';
+export const AI_SIDEBAR_TOOLBAR_BUTTON = 'ai-sidebar-toolbar-button';
// Check if the AI Assistant support is enabled.
export const isAiAssistantSupportEnabled = getFeatureAvailability( AI_ASSISTANT_SUPPORT_NAME );
+export const isAiSidebarToolbarButtonEnabled = getFeatureAvailability( AI_SIDEBAR_TOOLBAR_BUTTON );
/**
* Check if it is possible to enable the AI Assistant block and its features.
diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/text-blocks/with-ai-text-extension.tsx b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/text-blocks/with-ai-text-extension.tsx
index dec87d5785c9..d30d9455d21a 100644
--- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/text-blocks/with-ai-text-extension.tsx
+++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/extensions/text-blocks/with-ai-text-extension.tsx
@@ -22,6 +22,7 @@ import debugFactory from 'debug';
import useAutoScroll from '../../hooks/use-auto-scroll';
import useBlockModuleStatus from '../../hooks/use-block-module-status';
import { mapInternalPromptTypeToBackendPromptType } from '../../lib/prompt/backend-prompt';
+import { isAiSidebarToolbarButtonEnabled } from '../lib/can-ai-assistant-be-enabled';
import AiAssistantInput from './components/ai-assistant-input';
import AiAssistantExtensionToolbarDropdown from './components/ai-assistant-toolbar-dropdown';
import { getBlockHandler, InlineExtensionsContext } from './get-block-handler';
@@ -561,14 +562,16 @@ const blockEditWithAiComponents = createHigherOrderComponent( BlockEdit => {
/>
) }
-
-
-
+ { ! isAiSidebarToolbarButtonEnabled && (
+
+
+
+ ) }
>
);
diff --git a/projects/plugins/jetpack/extensions/index.json b/projects/plugins/jetpack/extensions/index.json
index 4d8417cd26c2..639bc2e1c46f 100644
--- a/projects/plugins/jetpack/extensions/index.json
+++ b/projects/plugins/jetpack/extensions/index.json
@@ -74,6 +74,7 @@
"ai-title-optimization-keywords-support",
"ai-response-feedback",
"ai-assistant-image-extension",
+ "ai-sidebar-toolbar-button",
"ai-seo-enhancer",
"ai-correct-spelling",
"paypal-payment-buttons",
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/ai-sidebar/class-jetpack-ai-sidebar.php b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/ai-sidebar/class-jetpack-ai-sidebar.php
index 019c4bacb747..4872c35d5a82 100644
--- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/ai-sidebar/class-jetpack-ai-sidebar.php
+++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/ai-sidebar/class-jetpack-ai-sidebar.php
@@ -17,13 +17,14 @@
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
-const AM_ASSET_BASE_PATH = 'widgets.wp.com/agents-manager/';
-const AI_SIDEBAR_ASSET_TRANSIENT = 'jetpack_ai_sidebar_asset';
-const AI_SIDEBAR_JS_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.min.js';
-const AI_SIDEBAR_CSS_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.css';
-const AI_SIDEBAR_RTL_CSS_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.rtl.css';
-const AI_SIDEBAR_PROVIDER_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.provider.mjs';
-const AI_SIDEBAR_AGENT_ID = 'wp-orchestrator';
+const AM_ASSET_BASE_PATH = 'widgets.wp.com/agents-manager/';
+const AI_SIDEBAR_ASSET_TRANSIENT = 'jetpack_ai_sidebar_asset';
+const AI_SIDEBAR_JS_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.min.js';
+const AI_SIDEBAR_CSS_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.css';
+const AI_SIDEBAR_RTL_CSS_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.rtl.css';
+const AI_SIDEBAR_PROVIDER_URL = 'https://' . AM_ASSET_BASE_PATH . 'jetpack-ai-sidebar.provider.mjs';
+const AI_SIDEBAR_AGENT_ID = 'wp-orchestrator';
+const AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION = 'ai-sidebar-toolbar-button';
/**
* Initializes the Agents Manager package and registers the Jetpack AI
@@ -71,6 +72,9 @@ public static function init(): void {
// never fired. Priority 250 runs after both jetpack-mu-wpcom and the
// Agents Manager package enqueue (priority 101).
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'maybe_patch_jetpack_ai_sidebar_preview_data' ), 250 );
+
+ // Let editor JS know when the Jetpack AI Sidebar toolbar button replaces the legacy AI toolbar.
+ add_action( 'jetpack_register_gutenberg_extensions', array( __CLASS__, 'register_toolbar_button_extension' ), 99 );
}
// ──────────────────────────────────────────────────
@@ -361,6 +365,7 @@ private static function get_jetpack_ai_sidebar_preview_config(): array {
'aiEditorialReview' => self::is_ai_editorial_review_enabled(),
'generateFeedback' => self::is_generate_feedback_enabled(),
'blockTransformations' => true,
+ 'blockToolbarButton' => false,
'optimizeTitleSuggestion' => self::is_optimize_title_suggestion_enabled(),
'chatHistory' => false,
'supportGuides' => false,
@@ -385,6 +390,35 @@ private static function get_jetpack_ai_sidebar_preview_config(): array {
);
}
+ /**
+ * Whether the Jetpack AI Sidebar toolbar button replaces the legacy AI toolbar.
+ *
+ * @return bool
+ */
+ public static function is_toolbar_button_enabled(): bool {
+ $preview_config = self::get_jetpack_ai_sidebar_preview_config();
+
+ return self::should_expose_sidebar()
+ && true === ( $preview_config['features']['blockToolbarButton'] ?? false );
+ }
+
+ /**
+ * Register the Jetpack AI Sidebar toolbar button feature.
+ *
+ * @return void
+ */
+ public static function register_toolbar_button_extension(): void {
+ if ( ! self::is_toolbar_button_enabled() ) {
+ \Jetpack_Gutenberg::set_extension_unavailable(
+ AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION,
+ 'jetpack_ai_sidebar_feature_disabled'
+ );
+ return;
+ }
+
+ \Jetpack_Gutenberg::set_extension_available( AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION );
+ }
+
/**
* Add Jetpack AI Sidebar-specific data to externally emitted Agents Manager payloads.
*
diff --git a/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php b/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php
index 73454c6c5539..538a0287292b 100644
--- a/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php
+++ b/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php
@@ -52,6 +52,7 @@ class Jetpack_AI_Sidebar_Test extends WP_UnitTestCase {
public function set_up() {
parent::set_up();
$this->reset_sidebar_hooks();
+ \Jetpack_Gutenberg::reset();
add_filter( 'jetpack_offline_mode', '__return_false' );
update_option( 'jetpack_offline_mode', '0' );
Status_Cache::clear();
@@ -93,6 +94,7 @@ public function tear_down() {
$GLOBALS['current_screen'] = $this->saved_screen;
$GLOBALS['wp_scripts'] = $this->saved_wp_scripts;
$GLOBALS['wp_styles'] = $this->saved_wp_styles;
+ \Jetpack_Gutenberg::reset();
parent::tear_down();
}
@@ -107,8 +109,31 @@ private function reset_sidebar_hooks() {
remove_all_filters( 'jetpack_ai_sidebar_preview_enabled' );
remove_all_filters( 'jetpack_ai_sidebar_preview_features' );
remove_all_filters( 'jetpack_ai_sidebar_agents_manager_data' );
+ remove_filter( 'jetpack_is_connection_ready', '__return_true', 1000 );
+ remove_filter( 'jetpack_gutenberg', '__return_true' );
+ remove_filter( 'jetpack_set_available_extensions', array( __CLASS__, 'get_sidebar_extension_allowlist' ) );
remove_action( 'admin_enqueue_scripts', array( Jetpack_AI_Sidebar::class, 'maybe_enqueue_abilities_script' ), 201 );
remove_action( 'admin_enqueue_scripts', array( Jetpack_AI_Sidebar::class, 'maybe_patch_jetpack_ai_sidebar_preview_data' ), 250 );
+ remove_action( 'jetpack_register_gutenberg_extensions', array( Jetpack_AI_Sidebar::class, 'register_toolbar_button_extension' ), 99 );
+ }
+
+ /**
+ * Limit Jetpack Gutenberg availability checks to the sidebar extension under test.
+ *
+ * @return array
+ */
+ public static function get_sidebar_extension_allowlist() {
+ return array( AiAssistantPlugin\AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION );
+ }
+
+ /**
+ * Enable Jetpack Gutenberg availability checks for the sidebar extension under test.
+ */
+ private function enable_sidebar_extension_availability_checks() {
+ add_filter( 'jetpack_is_connection_ready', '__return_true', 1000 );
+ add_filter( 'jetpack_gutenberg', '__return_true' );
+ add_filter( 'jetpack_set_available_extensions', array( __CLASS__, 'get_sidebar_extension_allowlist' ) );
+ Status_Cache::clear();
}
/**
@@ -192,6 +217,14 @@ private function simulate_self_hosted() {
Status_Cache::clear();
}
+ /**
+ * Mark legacy Jetpack AI block toolbar extensions as available.
+ */
+ private function make_legacy_block_toolbar_extensions_available() {
+ \Jetpack_Gutenberg::set_extension_available( 'ai-assistant-support' );
+ \Jetpack_Gutenberg::set_extension_available( 'ai-assistant-image-extension' );
+ }
+
/**
* Simulate the Big_Sky class existing, as it does when the Big Sky plugin is present.
*/
@@ -285,6 +318,10 @@ public function test_init_registers_hooks_by_default() {
has_action( 'admin_enqueue_scripts', array( Jetpack_AI_Sidebar::class, 'maybe_patch_jetpack_ai_sidebar_preview_data' ) ),
'maybe_patch_jetpack_ai_sidebar_preview_data should be hooked by default.'
);
+ $this->assertNotFalse(
+ has_action( 'jetpack_register_gutenberg_extensions', array( Jetpack_AI_Sidebar::class, 'register_toolbar_button_extension' ) ),
+ 'register_toolbar_button_extension should be hooked by default.'
+ );
}
/**
@@ -302,6 +339,10 @@ public function test_init_does_nothing_when_filter_is_false() {
has_filter( 'agents_manager_enabled_in_block_editor', array( Jetpack_AI_Sidebar::class, 'enable_agents_manager_in_post_editor' ) ),
'enable_agents_manager_in_post_editor should not be hooked when filter is false.'
);
+ $this->assertFalse(
+ has_action( 'jetpack_register_gutenberg_extensions', array( Jetpack_AI_Sidebar::class, 'register_toolbar_button_extension' ) ),
+ 'register_toolbar_button_extension should not be hooked when filter is false.'
+ );
}
/**
@@ -320,6 +361,10 @@ public function test_init_does_nothing_on_self_hosted() {
has_filter( 'agents_manager_enabled_in_block_editor', array( Jetpack_AI_Sidebar::class, 'enable_agents_manager_in_post_editor' ) ),
'enable_agents_manager_in_post_editor should not be hooked on a self-hosted site.'
);
+ $this->assertFalse(
+ has_action( 'jetpack_register_gutenberg_extensions', array( Jetpack_AI_Sidebar::class, 'register_toolbar_button_extension' ) ),
+ 'register_toolbar_button_extension should not be hooked when the preview gate is false.'
+ );
}
/**
@@ -366,6 +411,10 @@ public function test_init_registers_hooks_when_enabled() {
has_action( 'admin_enqueue_scripts', array( Jetpack_AI_Sidebar::class, 'maybe_patch_jetpack_ai_sidebar_preview_data' ) ),
'maybe_patch_jetpack_ai_sidebar_preview_data should be hooked when filter is true.'
);
+ $this->assertNotFalse(
+ has_action( 'jetpack_register_gutenberg_extensions', array( Jetpack_AI_Sidebar::class, 'register_toolbar_button_extension' ) ),
+ 'register_toolbar_button_extension should be hooked when filter is true.'
+ );
}
// ──────────────────────────────────────────────────
@@ -456,6 +505,121 @@ public function test_preview_filter_overrides_gate() {
$this->assertFalse( $this->gate_open() );
}
+ // ──────────────────────────────────────────────────
+ // Sidebar toolbar button tests
+ // ──────────────────────────────────────────────────
+
+ /**
+ * Test that the toolbar button stays disabled until its preview feature is released.
+ */
+ public function test_toolbar_button_disabled_by_default() {
+ $this->set_block_editor_screen();
+
+ $this->assertFalse( Jetpack_AI_Sidebar::is_toolbar_button_enabled() );
+ }
+
+ /**
+ * Test that the preview feature flag activates the toolbar button.
+ */
+ public function test_toolbar_button_enabled_by_preview_feature_flag() {
+ $this->set_block_editor_screen();
+ add_filter(
+ 'jetpack_ai_sidebar_preview_features',
+ static function ( $features ) {
+ $features['blockToolbarButton'] = true;
+ return $features;
+ }
+ );
+
+ $this->assertTrue( Jetpack_AI_Sidebar::is_toolbar_button_enabled() );
+ }
+
+ /**
+ * Test that the sidebar kill switch disables the toolbar button.
+ */
+ public function test_toolbar_button_respects_sidebar_kill_switch() {
+ $this->set_block_editor_screen();
+ add_filter(
+ 'jetpack_ai_sidebar_preview_features',
+ static function ( $features ) {
+ $features['blockToolbarButton'] = true;
+ return $features;
+ }
+ );
+ add_filter( 'jetpack_ai_sidebar_enabled', '__return_false' );
+
+ $this->assertFalse( Jetpack_AI_Sidebar::is_toolbar_button_enabled() );
+ }
+
+ /**
+ * Test that the active sidebar registers the toolbar button feature.
+ */
+ public function test_register_toolbar_button_extension_marks_feature_available() {
+ $this->set_block_editor_screen();
+ $this->make_legacy_block_toolbar_extensions_available();
+ $this->enable_sidebar_extension_availability_checks();
+ add_filter( 'jetpack_ai_sidebar_enabled', '__return_true' );
+ add_filter( 'jetpack_ai_sidebar_preview_enabled', '__return_true' );
+ add_filter(
+ 'jetpack_ai_sidebar_preview_features',
+ static function ( $features ) {
+ $features['blockToolbarButton'] = true;
+ return $features;
+ }
+ );
+
+ Jetpack_AI_Sidebar::register_toolbar_button_extension();
+
+ $this->assertTrue( \Jetpack_Gutenberg::is_available( AiAssistantPlugin\AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION ) );
+ $this->assertTrue( \Jetpack_Gutenberg::is_available( 'ai-assistant-support' ) );
+ $this->assertTrue( \Jetpack_Gutenberg::is_available( 'ai-assistant-image-extension' ) );
+ $this->assertTrue( \Jetpack_Gutenberg::get_availability()[ AiAssistantPlugin\AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION ]['available'] );
+ }
+
+ /**
+ * Test that the toolbar button feature stays unavailable until the preview feature is released.
+ */
+ public function test_register_toolbar_button_extension_skips_when_toolbar_button_disabled() {
+ $this->set_block_editor_screen();
+ $this->make_legacy_block_toolbar_extensions_available();
+ $this->enable_sidebar_extension_availability_checks();
+ add_filter(
+ 'jetpack_ai_sidebar_preview_features',
+ static function ( $features ) {
+ $features['blockToolbarButton'] = false;
+ return $features;
+ }
+ );
+
+ Jetpack_AI_Sidebar::register_toolbar_button_extension();
+
+ $this->assertFalse( \Jetpack_Gutenberg::is_available( AiAssistantPlugin\AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION ) );
+ $availability = \Jetpack_Gutenberg::get_availability()[ AiAssistantPlugin\AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION ];
+ $this->assertFalse( $availability['available'] );
+ $this->assertSame( 'jetpack_ai_sidebar_feature_disabled', $availability['unavailable_reason'] );
+ }
+
+ /**
+ * Test that the toolbar button feature stays unavailable outside the post editor.
+ */
+ public function test_register_toolbar_button_extension_skips_page_editor() {
+ $this->set_page_block_editor_screen();
+ $this->make_legacy_block_toolbar_extensions_available();
+ add_filter(
+ 'jetpack_ai_sidebar_preview_features',
+ static function ( $features ) {
+ $features['blockToolbarButton'] = true;
+ return $features;
+ }
+ );
+
+ Jetpack_AI_Sidebar::register_toolbar_button_extension();
+
+ $this->assertFalse( \Jetpack_Gutenberg::is_available( AiAssistantPlugin\AI_SIDEBAR_TOOLBAR_BUTTON_EXTENSION ) );
+ $this->assertTrue( \Jetpack_Gutenberg::is_available( 'ai-assistant-support' ) );
+ $this->assertTrue( \Jetpack_Gutenberg::is_available( 'ai-assistant-image-extension' ) );
+ }
+
// ──────────────────────────────────────────────────
// agents_manager_enabled_in_block_editor() tests
// ──────────────────────────────────────────────────
@@ -529,6 +693,7 @@ public function test_add_agents_manager_data_exposes_ai_editorial_review_enabled
$this->assertSame( true, $data['jetpackAiSidebar']['enabled'] );
$this->assertSame( true, $data['jetpackAiSidebar']['features']['aiEditorialReview'] );
$this->assertSame( true, $data['jetpackAiSidebar']['features']['blockTransformations'] );
+ $this->assertSame( false, $data['jetpackAiSidebar']['features']['blockToolbarButton'] );
// generateFeedback and optimizeTitleSuggestion are in development: off outside testing environments.
$this->assertSame( false, $data['jetpackAiSidebar']['features']['generateFeedback'] );
$this->assertSame( false, $data['jetpackAiSidebar']['features']['optimizeTitleSuggestion'] );
@@ -628,6 +793,7 @@ public function test_add_agents_manager_data_allows_preview_without_ai_editorial
$this->assertSame( true, $data['jetpackAiSidebar']['enabled'] );
$this->assertSame( false, $data['jetpackAiSidebar']['features']['aiEditorialReview'] );
$this->assertSame( true, $data['jetpackAiSidebar']['features']['blockTransformations'] );
+ $this->assertSame( false, $data['jetpackAiSidebar']['features']['blockToolbarButton'] );
}
/**