Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Fix PHP deprecated notice: cast $tier to int before using as array offset in get_long_description_by_usage_tier() to handle null from get_next_usage_tier()
17 changes: 9 additions & 8 deletions projects/packages/my-jetpack/src/products/class-jetpack-ai.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,17 +234,18 @@ public static function get_description() {
/**
* Get the internationalized usage tier long description by tier
*
* @param int $tier The usage tier.
* @param int|null $tier The usage tier.
* @return string
*/
public static function get_long_description_by_usage_tier( $tier ) {
$long_descriptions = array(
1 => __( 'Jetpack AI Assistant brings the power of AI right into your WordPress editor, letting your content creation soar to new heights.', 'jetpack-my-jetpack' ),
100 => __( 'The most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' ),
);
$tiered_description = __( 'Upgrade and increase the amount of your available monthly requests to continue using the most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' );

return isset( $long_descriptions[ $tier ] ) ? $long_descriptions[ $tier ] : $tiered_description;
switch ( (int) $tier ) {
case 1:
return __( 'Jetpack AI Assistant brings the power of AI right into your WordPress editor, letting your content creation soar to new heights.', 'jetpack-my-jetpack' );
case 100:
return __( 'The most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' );
default:
return __( 'Upgrade and increase the amount of your available monthly requests to continue using the most advanced AI technology Jetpack has to offer.', 'jetpack-my-jetpack' );
}
}

/**
Expand Down
29 changes: 26 additions & 3 deletions projects/plugins/jetpack/_inc/client/ai/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import McpWrite from './mcp/write';

const { blogId, activityLogUrl, apiRoot, apiNonce } = window?.jetpackAiSettings ?? {};

const VALID_VIEWS = [ 'read', 'write', 'setup' ];

const getViewFromHash = () => {
const hash = window.location.hash.replace( /^#\//, '' );
return VALID_VIEWS.includes( hash ) ? hash : 'hub';
};

const VIEW_TITLES = {
hub: 'AI', // "AI" is a product name and should not be translated.
read: __( 'Read', 'jetpack' ),
Expand Down Expand Up @@ -71,11 +78,19 @@ function Breadcrumbs( { view, onNavigate } ) {
* @return {object} Component markup.
*/
export default function App() {
const [ view, setView ] = useState( 'hub' );
const [ view, setView ] = useState( getViewFromHash );
const [ saveError, setSaveError ] = useState( null );
const { isLoading, savingToolIds, mcpAbilities, hasMcpAccess, error, updateMcpAbilities } =
useMcpSettings();

useEffect( () => {
// Tag the initial history entry so the popstate handler can restore the hub view.
window.history.replaceState( { view: getViewFromHash() }, '' );
const handlePopState = event => setView( event.state?.view ?? 'hub' );
window.addEventListener( 'popstate', handlePopState );
return () => window.removeEventListener( 'popstate', handlePopState );
}, [] );

useEffect( () => {
if ( ! isLoading && hasMcpAccess ) {
analytics.tracks.recordEvent( 'jetpack_mcp_settings_viewed' );
Expand All @@ -93,7 +108,15 @@ export default function App() {
);

const dismissSaveError = useCallback( () => setSaveError( null ), [] );
const navigateBack = useCallback( () => setView( 'hub' ), [] );

const navigateToView = useCallback( newView => {
window.history.pushState( { view: newView }, '', '#/' + newView );
setView( newView );
}, [] );

// The breadcrumb back link mirrors the browser Back button so the history
// entry for the sub-view is popped rather than a new hub entry being pushed.
const navigateBack = useCallback( () => window.history.back(), [] );

const isSubView = view !== 'hub';

Expand Down Expand Up @@ -148,7 +171,7 @@ export default function App() {
blogId={ blogId }
activityLogUrl={ activityLogUrl }
savingToolIds={ savingToolIds }
onNavigate={ setView }
onNavigate={ navigateToView }
onUpdate={ handleUpdate }
/>
) }
Expand Down
72 changes: 71 additions & 1 deletion projects/plugins/jetpack/_inc/client/ai/mcp/categories.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,26 @@ export const CATEGORY_ORDER = [
];

const SUB_CATEGORIES = {
// Posts sub-categories
POSTS: __( 'Posts', 'jetpack' ),
COMMENTS: __( 'Comments', 'jetpack' ),
CATEGORIES_TAGS: __( 'Categories & tags', 'jetpack' ),
// Sites sub-categories
SITES: __( 'Sites', 'jetpack' ),
PLUGINS: __( 'Plugins', 'jetpack' ),
MEDIA: __( 'Media', 'jetpack' ),
SITE_SETTINGS: __( 'Site settings', 'jetpack' ),
ANALYTICS: __( 'Analytics', 'jetpack' ),
// Account sub-categories
ACCOUNT: __( 'Account', 'jetpack' ),
NOTIFICATIONS: __( 'Notifications', 'jetpack' ),
// Design sub-categories
THEMES: __( 'Themes', 'jetpack' ),
PATTERNS: __( 'Patterns', 'jetpack' ),
TEMPLATES: __( 'Templates', 'jetpack' ),
GLOBAL_STYLES: __( 'Global styles', 'jetpack' ),
NAVIGATION: __( 'Navigation', 'jetpack' ),
BLOCKS: __( 'Blocks', 'jetpack' ),
};

export const SUB_CATEGORY_ORDER = {
Expand All @@ -49,11 +60,20 @@ export const SUB_CATEGORY_ORDER = {
],
[ DISPLAY_CATEGORIES.SITES ]: [
SUB_CATEGORIES.SITES,
SUB_CATEGORIES.PLUGINS,
SUB_CATEGORIES.SITE_SETTINGS,
SUB_CATEGORIES.MEDIA,
SUB_CATEGORIES.ANALYTICS,
],
[ DISPLAY_CATEGORIES.ACCOUNT ]: [ SUB_CATEGORIES.ACCOUNT, SUB_CATEGORIES.NOTIFICATIONS ],
[ DISPLAY_CATEGORIES.DESIGN ]: [
SUB_CATEGORIES.THEMES,
SUB_CATEGORIES.PATTERNS,
SUB_CATEGORIES.TEMPLATES,
SUB_CATEGORIES.GLOBAL_STYLES,
SUB_CATEGORIES.NAVIGATION,
SUB_CATEGORIES.BLOCKS,
],
};

const API_CATEGORY_TO_DISPLAY = {
Expand All @@ -76,20 +96,40 @@ const API_CATEGORY_TO_DISPLAY = {
};

const API_CATEGORY_TO_SUB_CATEGORY = {
// Posts card sub-categories
posts: SUB_CATEGORIES.POSTS,
comments: SUB_CATEGORIES.COMMENTS,
'categories-tags': SUB_CATEGORIES.CATEGORIES_TAGS,
// Sites card sub-categories
sites: SUB_CATEGORIES.SITES,
media: SUB_CATEGORIES.MEDIA,
users: SUB_CATEGORIES.SITE_SETTINGS,
plugins: SUB_CATEGORIES.SITE_SETTINGS,
plugins: SUB_CATEGORIES.PLUGINS,
'site-settings': SUB_CATEGORIES.SITE_SETTINGS,
analytics: SUB_CATEGORIES.ANALYTICS,
// Account card sub-categories
account: SUB_CATEGORIES.ACCOUNT,
notifications: SUB_CATEGORIES.NOTIFICATIONS,
billing: SUB_CATEGORIES.ACCOUNT,
};

// Design-card tools all share `design` as their API category, so sub-groups within the
// Design card are derived from tool ID prefixes. This also covers `sites`-category tools
// that are routed to the Design card (navigation, menus, themes).
const TOOL_ID_PREFIX_TO_DESIGN_SUB_CATEGORY = {
'wpcom-mcp/theme-': SUB_CATEGORIES.THEMES,
'wpcom-mcp/themes-': SUB_CATEGORIES.THEMES,
'wpcom-mcp/patterns-': SUB_CATEGORIES.PATTERNS,
'wpcom-mcp/synced-patterns-': SUB_CATEGORIES.PATTERNS,
'wpcom-mcp/templates-': SUB_CATEGORIES.TEMPLATES,
'wpcom-mcp/template-parts-': SUB_CATEGORIES.TEMPLATES,
'wpcom-mcp/global-styles-': SUB_CATEGORIES.GLOBAL_STYLES,
'wpcom-mcp/navigation-': SUB_CATEGORIES.NAVIGATION,
'wpcom-mcp/menus-': SUB_CATEGORIES.NAVIGATION,
'wpcom-mcp/menu-items-': SUB_CATEGORIES.NAVIGATION,
'wpcom-mcp/blocks-': SUB_CATEGORIES.BLOCKS,
};

/**
* Get the display sub-category name for a tool.
*
Expand All @@ -99,6 +139,24 @@ const API_CATEGORY_TO_SUB_CATEGORY = {
*/
export function getSubCategory( toolId, ability ) {
const apiCategory = ability?.category;

// Design-card tools use tool ID prefix for sub-grouping. This covers both
// 'design'-category tools and 'sites'-category tools that are routed to the
// Design card by getDisplayCategory (e.g. navigation, menus, themes).
if ( apiCategory === 'design' || apiCategory === 'sites' ) {
for ( const [ prefix, subCategory ] of Object.entries(
TOOL_ID_PREFIX_TO_DESIGN_SUB_CATEGORY
) ) {
if ( toolId.startsWith( prefix ) ) {
return subCategory;
}
}
// Tools in these API categories should still be rendered even when their
// IDs do not match a known Design prefix, so fall back to the category's
// default sub-category instead of returning undefined.
return API_CATEGORY_TO_SUB_CATEGORY[ apiCategory ];
}

if ( apiCategory ) {
return API_CATEGORY_TO_SUB_CATEGORY[ apiCategory ];
}
Expand All @@ -119,12 +177,24 @@ export function isWriteTool( toolId, ability ) {
/**
* Get the display category name for a tool.
*
* For 'design' and 'sites' category tools, check if the tool ID prefix indicates
* it belongs in the Design card (navigation, menus, themes mirror calypso's approach).
*
* @param {string} toolId - Tool identifier.
* @param {object} ability - Tool descriptor from the API.
* @return {string} Display category name, falling back to Uncategorized.
*/
export function getDisplayCategory( toolId, ability ) {
const apiCategory = ability?.category;

if ( apiCategory === 'design' || apiCategory === 'sites' ) {
for ( const prefix of Object.keys( TOOL_ID_PREFIX_TO_DESIGN_SUB_CATEGORY ) ) {
if ( toolId.startsWith( prefix ) ) {
return DISPLAY_CATEGORIES.DESIGN;
}
}
}

if ( apiCategory && API_CATEGORY_TO_DISPLAY[ apiCategory ] ) {
return API_CATEGORY_TO_DISPLAY[ apiCategory ];
}
Expand Down
8 changes: 4 additions & 4 deletions projects/plugins/jetpack/_inc/client/ai/mcp/setup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default function McpSetup() {
</Text>

{ selectedClient === 'claude' && (
<ol className="jetpack-ai-mcp-setup__steps">
<ul className="jetpack-ai-mcp-setup__steps">
<li>
<Text as="p" variant="muted">
{ createInterpolateElement( __( 'Open <ClaudeSettings/>.', 'jetpack' ), {
Expand All @@ -151,7 +151,7 @@ export default function McpSetup() {
{ __( 'Select WordPress.com and follow the prompts.', 'jetpack' ) }
</Text>
</li>
</ol>
</ul>
) }

{ selectedClient === 'claude-code' && (
Expand All @@ -162,7 +162,7 @@ export default function McpSetup() {
'jetpack'
) }
</Text>
<ol className="jetpack-ai-mcp-setup__steps">
<ul className="jetpack-ai-mcp-setup__steps">
<li>
<Text as="p" variant="muted">
{ createInterpolateElement(
Expand Down Expand Up @@ -206,7 +206,7 @@ export default function McpSetup() {
) }
</Text>
</li>
</ol>
</ul>
</Stack>
) }

Expand Down
21 changes: 21 additions & 0 deletions projects/plugins/jetpack/_inc/client/ai/mcp/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
display: inline-flex;
align-items: center;
gap: 4px;
vertical-align: center;
Comment thread
eoigal marked this conversation as resolved.
}

&__category-header-row {
Expand All @@ -91,17 +92,37 @@
justify-content: space-between;
align-items: center;
width: 100%;

// The ToggleControl label uses line-height equal to the toggle height for
// vertical alignment, but the FlexBlock wrapper defaults to align-items:
// stretch. Force center so the "Enable all" text sits mid-toggle.
.components-toggle-control__label {
display: flex;
align-items: center;
}
}

&__tool-group-divider {
margin: 0;
}

// Tighten the gap between each tool toggle's help text and the next toggle.
// WP's BaseControl adds margin-top: 8px on the help text; bump overall
// row gap down slightly so the combined visual spacing feels even.
.components-base-control__help {
margin-top: 4px;
}

&__access-card {
overflow: hidden;
}
}

// wp-admin resets list-style on ul; restore bullets for the steps list.
ul.jetpack-ai-mcp-setup__steps {
list-style: disc;
}

.jetpack-ai-mcp-setup {

&__steps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

use Automattic\Jetpack\Admin_UI\Admin_Menu;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;

Expand Down Expand Up @@ -86,13 +85,12 @@ public function page_admin_scripts() {
// Use the plain hostname for the Atomic activity log URL — get_site_suffix() can
// include '::' for subdirectory installs, which would break the URL. This matches
// the approach used by jetpack-mu-wpcom for the sidebar Activity Log link.
$site_host = wp_parse_url( home_url(), PHP_URL_HOST );
// On Atomic, jetpack-mu-wpcom replaces the cloud redirect with a direct WPCOM URL.
// Mirror that behaviour here so the activity log link resolves consistently.
$site_host = wp_parse_url( home_url(), PHP_URL_HOST );
$activity_log_site = ( is_string( $site_host ) && '' !== $site_host ) ? $site_host : $site_suffix;
$activity_log_url = ( new Host() )->is_woa_site()
// On Atomic link to WPCOM activity log; on self-hosted link to the local wp-admin page.
$activity_log_url = ( new Host() )->is_woa_site()
? 'https://wordpress.com/activity-log/' . $activity_log_site
: Redirect::get_url( 'cloud-activity-log-wp-menu', array( 'site' => $blog_id ? $blog_id : $site_suffix ) );
: admin_url( 'admin.php?page=jetpack-activity-log' );

wp_enqueue_script(
'jetpack-ai-admin',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: bugfix

Improved the MCP settings UI by fixing browser Back button navigation, changing setup instructions to unordered lists, correcting the 'All enabled' label vertical alignment, and tightening toggle help-text spacing.
Loading