diff --git a/includes/Abstracts/Abstract_Feature.php b/includes/Abstracts/Abstract_Feature.php
index fe52055c3..eb322425a 100644
--- a/includes/Abstracts/Abstract_Feature.php
+++ b/includes/Abstracts/Abstract_Feature.php
@@ -169,7 +169,7 @@ public function get_category(): string {
* {@inheritDoc}
*/
final public function is_globally_enabled(): bool {
- return (bool) get_option( Settings_Registration::GLOBAL_OPTION, false );
+ return (bool) get_option( Settings_Registration::GLOBAL_OPTION, true );
}
/**
diff --git a/routes/ai-home/stage.tsx b/routes/ai-home/stage.tsx
index fb51ef486..46b4f5813 100644
--- a/routes/ai-home/stage.tsx
+++ b/routes/ai-home/stage.tsx
@@ -2,16 +2,7 @@
* WordPress dependencies
*/
import { Page } from '@wordpress/admin-ui';
-import {
- Button,
- Card,
- Icon,
- Link,
- Notice,
- Popover,
- Stack,
- VisuallyHidden,
-} from '@wordpress/ui';
+import { Button, Card, Link, Notice, Stack } from '@wordpress/ui';
import {
DropdownMenu,
MenuGroup,
@@ -32,7 +23,6 @@ import { __, _n, sprintf } from '@wordpress/i18n';
import {
check as checkIcon,
download as downloadIcon,
- info as infoIcon,
tool as toolIcon,
upload as uploadIcon,
} from '@wordpress/icons';
@@ -96,8 +86,6 @@ interface PageData {
}
const FEATURE_SETTING_PATTERN = /^wpai_feature_(.+)_enabled$/;
-const GLOBAL_FIELD_ID = 'wpai_features_enabled';
-const noop = () => {};
function isRecord( value: unknown ): value is Record< string, unknown > {
return typeof value === 'object' && value !== null;
@@ -270,40 +258,6 @@ const STABLE_FEATURE_DEFINITIONS: FeatureData[] = ( () => {
return unique;
} )();
-interface InfoTipProps {
- content: string;
-}
-
-function InfoTip( { content }: InfoTipProps ) {
- const title = __( 'More information', 'ai' );
-
- return (
-
-
-
-
- }
- className="ai-settings-page__infotip-popover"
- >
-
- }>
- { title }
-
-
- { content }
-
-
-
- );
-}
-
function buildToggleMessage(
edits: Record< string, unknown >,
featureDefinitions: FeatureData[]
@@ -362,11 +316,6 @@ function buildToggleMessage(
return __( 'Settings saved.', 'ai' );
}
- if ( entry[ 0 ] === GLOBAL_FIELD_ID ) {
- return entry[ 1 ]
- ? __( 'AI enabled.', 'ai' )
- : __( 'AI disabled.', 'ai' );
- }
const feature = featureDefinitions.find(
( f ) => f.settingName === entry[ 0 ]
);
@@ -378,29 +327,14 @@ function buildToggleMessage(
sprintf( __( '%s disabled.', 'ai' ), label );
}
-function DisabledToggle( { field, data }: DataFormControlProps< AISettings > ) {
- return (
-
- );
-}
-
interface SectionActionsProps extends DataFormControlProps< AISettings > {
experimentSettings: string[];
- globalEnabled: boolean;
onBulkChange: ( edits: Record< string, boolean > ) => void;
}
function SectionActions( {
experimentSettings,
data,
- globalEnabled,
onBulkChange,
}: SectionActionsProps ) {
const allEnabled = useMemo( () => {
@@ -453,7 +387,7 @@ function SectionActions( {
variant="outline"
size="compact"
onClick={ handleEnableAll }
- disabled={ ! globalEnabled || allEnabled }
+ disabled={ allEnabled }
>
{ __( 'Enable all', 'ai' ) }
@@ -461,7 +395,7 @@ function SectionActions( {
variant="outline"
size="compact"
onClick={ handleDisableAll }
- disabled={ ! globalEnabled || allDisabled }
+ disabled={ allDisabled }
>
{ __( 'Disable all', 'ai' ) }
@@ -661,16 +595,11 @@ function VisualCardToggle( {
onChange,
}: DataFormControlProps< AISettings > ) {
const feature = VISUAL_CARD_FEATURES.get( field.id );
- const globalEnabled = !! data[ GLOBAL_FIELD_ID ];
const checked = !! field.getValue( { item: data } );
const isDeveloperMode = useDeveloperModeContext();
return (
-
+
{ feature?.image && (
onChange( { [ field.id ]: value } )
}
- disabled={ ! globalEnabled }
help={ field.description }
/>
- { globalEnabled && checked && isDeveloperMode && feature && (
+ { checked && isDeveloperMode && feature && (
{
- const settingKeys = new Set< string >( [ GLOBAL_FIELD_ID ] );
+ const settingKeys = new Set< string >();
for ( const feature of featureDefinitions ) {
settingKeys.add( feature.settingName );
@@ -797,12 +725,6 @@ function AISettingsPage() {
return aiSettings;
}, [ aiSettingKeys, editedRecord ] );
- const globalEnabled = Boolean( data[ GLOBAL_FIELD_ID ] );
- const globalToggleDescription = __(
- 'Control whether AI is enabled for your site. When disabled, all features and experiments will be inactive regardless of their individual settings.',
- 'ai'
- );
-
const handleChange = useCallback(
async ( edits: Record< string, unknown > ) => {
const keys = Object.keys( edits );
@@ -874,7 +796,6 @@ function AISettingsPage() {
),
@@ -892,8 +813,6 @@ function AISettingsPage() {
if ( VISUAL_CARD_FEATURES.has( feature.settingName ) ) {
baseField.Edit = VisualCardToggle;
- } else if ( ! globalEnabled ) {
- baseField.Edit = DisabledToggle;
} else if ( feature.settingsFields.length > 0 ) {
baseField.Edit = FeatureToggleWithSettings;
} else {
@@ -912,7 +831,7 @@ function AISettingsPage() {
} );
return [ ...sectionActionsFields, ...featureFields ];
- }, [ featureDefinitions, featureGroups, globalEnabled, handleChange ] );
+ }, [ featureDefinitions, featureGroups, handleChange ] );
const form = useMemo< Form >( () => {
const showcaseChildren: string[] = [];
@@ -1019,19 +938,6 @@ function AISettingsPage() {
) }
actions={
<>
-
- {
- void handleChange( {
- [ GLOBAL_FIELD_ID ]: checked,
- } );
- } }
- disabled={ isLoading }
- />
-
-
{
admin,
page,
} ) => {
- // Start with AI disabled so we can confirm cancelling leaves it untouched.
- await disableExperiments( admin, page );
+ // Ensure Title Generation is disabled so we can confirm cancelling leaves it untouched.
+ await disableExperiment( admin, page, 'Title Generation' );
const filePath = writeTempExportFile( {
- wpai_features_enabled: true,
+ 'wpai_feature_title-generation_enabled': true,
} );
tempFiles.push( filePath );
@@ -194,20 +193,20 @@ test.describe( 'Settings import/export', () => {
await dialog.getByRole( 'button', { name: 'Cancel' } ).click();
await expect( dialog ).not.toBeVisible();
- // Global AI toggle must remain disabled since the import was cancelled.
- await expect( page.getByLabel( 'Enable AI' ) ).not.toBeChecked();
+ // Title Generation toggle must remain disabled since the import was cancelled.
+ await expect( page.getByLabel( 'Title Generation' ) ).not.toBeChecked();
} );
test( 'Confirming the import applies settings without a page reload', async ( {
admin,
page,
} ) => {
- // Start with AI disabled so the imported value is a visible change.
- await disableExperiments( admin, page );
- await expect( page.getByLabel( 'Enable AI' ) ).not.toBeChecked();
+ // Ensure Title Generation is disabled so the imported value is a visible change.
+ await disableExperiment( admin, page, 'Title Generation' );
+ await expect( page.getByLabel( 'Title Generation' ) ).not.toBeChecked();
const filePath = writeTempExportFile( {
- wpai_features_enabled: true,
+ 'wpai_feature_title-generation_enabled': true,
} );
tempFiles.push( filePath );
@@ -234,12 +233,12 @@ test.describe( 'Settings import/export', () => {
// The toggle should reflect the imported value immediately, with no
// manual page reload required (regression guard for the stale
// core-data cache/infinite-spinner issue).
- await expect( page.getByLabel( 'Enable AI' ) ).toBeChecked( {
+ await expect( page.getByLabel( 'Title Generation' ) ).toBeChecked( {
timeout: 10000,
} );
- // Cleanup.
- await enableExperiments( admin, page );
+ // Cleanup: restore Title Generation to disabled.
+ await disableExperiment( admin, page, 'Title Generation' );
} );
test( 'Rejects an invalid file and shows an error notice', async ( {
diff --git a/tests/e2e/specs/admin/settings.spec.js b/tests/e2e/specs/admin/settings.spec.js
index 00a33ce63..9afb47b88 100644
--- a/tests/e2e/specs/admin/settings.spec.js
+++ b/tests/e2e/specs/admin/settings.spec.js
@@ -9,7 +9,6 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
const {
clearConnectors,
seedCredentials,
- disableExperiments,
disableExperiment,
enableExperiment,
enableExperiments,
@@ -101,27 +100,11 @@ test.describe( 'Plugin settings', () => {
.click();
} );
- test( 'Can turn on Experiments', async ( { admin, page } ) => {
- // Globally disable experiments.
- await disableExperiments( admin, page );
-
- // Ensure global AI setting is disabled.
- await expect( page.getByLabel( 'Enable AI' ) ).not.toBeChecked();
-
- // Ensure feature toggles are disabled when AI is disabled.
- await expect(
- page
- .locator(
- '#ai-wp-admin-app .components-form-toggle.is-disabled'
- )
- .first()
- ).toBeVisible();
-
- // Globally turn on experiments.
- await enableExperiments( admin, page );
-
- // Ensure global AI setting is enabled.
- await expect( page.getByLabel( 'Enable AI' ) ).toBeChecked();
+ test( 'Settings page displays experiment sections', async ( {
+ admin,
+ page,
+ } ) => {
+ await visitSettingsPage( admin );
// Ensure we see the editor experiments section.
await expect(
@@ -140,13 +123,13 @@ test.describe( 'Plugin settings', () => {
} ) => {
// Use a fixed desktop viewport so the admin menu is at full width and
// snackbar placement is deterministic.
- await page.setViewportSize( { width: 1280, height: 800 } );
+ await page.setViewportSize( { width: 1440, height: 800 } );
await visitSettingsPage( admin );
- // Toggle the global setting to trigger a snackbar.
- const globalToggle = page.getByLabel( 'Enable AI' );
- await expect( globalToggle ).toBeVisible( { timeout: 10000 } );
- await globalToggle.click();
+ // Toggle a feature setting to trigger a snackbar.
+ const featureToggle = page.getByLabel( 'Title Generation' );
+ await expect( featureToggle ).toBeVisible( { timeout: 10000 } );
+ await featureToggle.click();
const snackbar = page.getByTestId( 'snackbar' ).first();
await expect( snackbar ).toBeVisible();
@@ -162,6 +145,9 @@ test.describe( 'Plugin settings', () => {
expect( snackBox.x + snackBox.width ).toBeLessThanOrEqual(
contentBox.x
);
+
+ // Restore toggle state.
+ await featureToggle.click();
} );
test( 'Inline settings retain pending edits when another toggle auto-saves', async ( {
@@ -312,28 +298,6 @@ test.describe( 'Plugin settings', () => {
}
} );
- test( 'Cannot bulk manage experiments when global AI is disabled', async ( {
- admin,
- page,
- } ) => {
- // Disable global AI.
- await disableExperiments( admin, page );
-
- // Verify both buttons are disabled.
- const enableAllButton = getEnableAllButton(
- page,
- EXPERIMENT_GROUPS.editor
- );
-
- const disableAllButton = getDisableAllButton(
- page,
- EXPERIMENT_GROUPS.editor
- );
-
- await expect( enableAllButton ).toBeDisabled();
- await expect( disableAllButton ).toBeDisabled();
- } );
-
test( 'Each experiment group has its own bulk action buttons', async ( {
admin,
page,
@@ -715,35 +679,30 @@ test.describe( 'Plugin settings', () => {
admin,
page,
} ) => {
- // Globally turn on experiments so the Image Generation feature can be enabled.
+ // Turn on experiments so the Image Generation feature can be enabled.
await enableExperiments( admin, page );
// Enable the visual Image Generation feature card.
await enableExperiment( admin, page, 'Image Generation and Editing' );
- // Turn on model selection while AI is globally enabled.
+ // Turn on model selection.
await enableModelSelection( page );
- // Globally disable AI. The feature card remains checked, but inactive.
- await disableExperiments( admin, page );
+ // Disable the visual feature card.
+ await disableExperiment( admin, page, 'Image Generation and Editing' );
- const disabledImageGenerationCard = page.locator(
- '.ai-showcase-card--disabled',
- {
- has: page.getByText( 'Image Generation and Editing' ),
- }
- );
+ const imageGenerationCard = page.locator( '.ai-showcase-card', {
+ has: page.getByText( 'Image Generation and Editing' ),
+ } );
- await expect( disabledImageGenerationCard ).toBeVisible();
+ await expect( imageGenerationCard ).toBeVisible();
// The disabled visual feature card should not expose active provider/model controls.
await expect(
- disabledImageGenerationCard.locator( '.ai-developer-mode-fields' )
+ imageGenerationCard.locator( '.ai-developer-mode-fields' )
).not.toBeVisible();
// Restore state.
- await enableExperiments( admin, page );
await disableModelSelection( page );
- await disableExperiment( admin, page, 'Image Generation and Editing' );
} );
} );
diff --git a/tests/e2e/utils/helpers.ts b/tests/e2e/utils/helpers.ts
index 43899193f..c01c7f615 100644
--- a/tests/e2e/utils/helpers.ts
+++ b/tests/e2e/utils/helpers.ts
@@ -163,47 +163,58 @@ export const clearConnector = async (
export const disableExperiments = async ( admin: Admin, page: Page ) => {
await visitSettingsPage( admin );
- // Wait for page to fully load before finding the global toggle.
- const globalToggle = page.getByLabel( 'Enable AI' );
- await expect( globalToggle ).toBeVisible( { timeout: 10000 } );
- await expect( globalToggle ).toBeEnabled( { timeout: 10000 } );
+ const disableAllButtons = page.getByRole( 'button', {
+ name: 'Disable all',
+ } );
+ await expect( disableAllButtons.first() ).toBeVisible( { timeout: 10000 } );
- // Nothing to do if experiments are already disabled.
- if ( ! ( await globalToggle.isChecked() ) ) {
- return;
+ const count = await disableAllButtons.count();
+ for ( let i = 0; i < count; i++ ) {
+ const button = disableAllButtons.nth( i );
+ if ( await button.isEnabled() ) {
+ const savePromise = page.waitForResponse(
+ ( response ) =>
+ response.url().includes( '/wp/v2/settings' ) &&
+ response.request().method() === 'POST' &&
+ response.status() === 200
+ );
+ await button.click();
+ await savePromise;
+ await expect( button ).toBeDisabled( { timeout: 10000 } );
+ }
+ }
+
+ const showcaseToggles = page.locator(
+ '.ai-showcase-card input[type="checkbox"]'
+ );
+ const showcaseCount = await showcaseToggles.count();
+ for ( let i = 0; i < showcaseCount; i++ ) {
+ const toggle = showcaseToggles.nth( i );
+ if ( await toggle.isChecked() ) {
+ const savePromise = page.waitForResponse(
+ ( response ) =>
+ response.url().includes( '/wp/v2/settings' ) &&
+ response.request().method() === 'POST' &&
+ response.status() === 200
+ );
+ await toggle.uncheck();
+ await savePromise;
+ await expect( toggle ).not.toBeChecked( { timeout: 10000 } );
+ }
}
- await globalToggle.uncheck();
- await expect(
- page.locator( '.components-snackbar__content', {
- hasText: 'AI disabled.',
- } )
- ).toBeVisible();
};
/**
- * Globally enables experiments.
+ * Ensures AI experiments are globally enabled.
+ *
+ * With the "Enable AI" header toggle removed, the plugin is enabled by default.
*
* @param admin The admin fixture from the test context.
* @param page The page object.
*/
-export const enableExperiments = async ( admin: Admin, page: Page ) => {
- await visitSettingsPage( admin );
-
- // Wait for page to fully load before finding the global toggle.
- const globalToggle = page.getByLabel( 'Enable AI' );
- await expect( globalToggle ).toBeVisible( { timeout: 10000 } );
- await expect( globalToggle ).toBeEnabled( { timeout: 10000 } );
-
- // Nothing to do if experiments are already enabled.
- if ( await globalToggle.isChecked() ) {
- return;
- }
- await globalToggle.check();
- await expect(
- page.locator( '.components-snackbar__content', {
- hasText: 'AI enabled.',
- } )
- ).toBeVisible();
+export const enableExperiments = async ( admin?: Admin, page?: Page ) => {
+ void admin;
+ void page;
};
/**