Commit 057a68972ce for woocommerce

commit 057a68972ce106b2afa387f991fc83a861c6eebd
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Tue Oct 20 18:12:14 2026 +0200

    Unify Product Title and Product Summary block variations (#69785)

    * Update single product templates to use the current title block

    * Add changelog entry for single product template update

    * fix template

    * consolidate variations registration

    * clean up changelog

    * update context

    * Scope Product Collection context to collection descendants

    * Add changelog entry for Product Collection context scoping

    * improve guard

    * improve logic

    * improve code

    * fix test

    * Update plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php

    Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>

    * remove changelog

    * use constant

    ---------

    Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/unify-product-element-variations b/plugins/woocommerce/changelog/unify-product-element-variations
new file mode 100644
index 00000000000..d39691c3f6a
--- /dev/null
+++ b/plugins/woocommerce/changelog/unify-product-element-variations
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Unify Product Title and Product Summary variations across product blocks and templates.
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/single-product.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/single-product.ts
index f2f09a4e222..5a8dbb06867 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/single-product.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/single-product.ts
@@ -3,7 +3,7 @@
  */
 import { __, sprintf } from '@wordpress/i18n';
 import { getSetting } from '@woocommerce/settings';
-import { VARIATION_NAME as PRODUCT_TITLE_VARIATION_NAME } from '@woocommerce/blocks/product-query/variations/elements/product-title';
+import { PRODUCT_TITLE_VARIATION_NAME } from '@woocommerce/product-elements/variations/constants';
 import {
 	INNER_BLOCKS_PRODUCT_TEMPLATE as productCollectionInnerBlocksTemplate,
 	DEFAULT_ATTRIBUTES as productCollectionDefaultAttributes,
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/constants.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/constants.ts
index 7e8af19238c..c488d3f8091 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/constants.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/constants.ts
@@ -9,7 +9,7 @@ import { __ } from '@wordpress/i18n';
 /**
  * Internal dependencies
  */
-import { VARIATION_NAME as PRODUCT_TITLE_VARIATION_NAME } from '../product-collection/variations/elements/product-title';
+import { PRODUCT_TITLE_VARIATION_NAME } from '../product-elements-blocks/variations/constants';

 export const DEFAULT_EDITOR_SIZE = {
 	height: 500,
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/featured-product/deprecated.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/featured-product/deprecated.tsx
index 9293339721f..6955392100f 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/featured-product/deprecated.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/featured-product/deprecated.tsx
@@ -3,6 +3,7 @@
  */
 import { BlockInstance, createBlock } from '@wordpress/blocks';
 import { InnerBlocks } from '@wordpress/block-editor';
+import { PRODUCT_TITLE_VARIATION_NAME } from '@woocommerce/product-elements/variations/constants';

 /**
  * Internal dependencies
@@ -77,8 +78,7 @@ const v1 = {
 				level: 2,
 				isLink: false,
 				textAlign: 'center',
-				__woocommerceNamespace:
-					'woocommerce/product-collection/product-title',
+				__woocommerceNamespace: PRODUCT_TITLE_VARIATION_NAME,
 			} )
 		);

diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/migration-products-to-product-collection/migration-from-products-to-product-collection.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/migration-products-to-product-collection/migration-from-products-to-product-collection.ts
index c01d54e7a85..4903de73b6b 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/migration-products-to-product-collection/migration-from-products-to-product-collection.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/migration-products-to-product-collection/migration-from-products-to-product-collection.ts
@@ -18,6 +18,10 @@ import type {
 	PostTemplateLayout,
 	PostTemplateLayoutTypes,
 } from './types';
+import {
+	PRODUCT_TITLE_VARIATION_NAME,
+	PRODUCT_SUMMARY_VARIATION_NAME,
+} from '../product-elements-blocks/variations/constants';
 import { DEFAULT_ATTRIBUTES } from '../product-collection/constants';
 import {
 	LayoutOptions,
@@ -80,8 +84,7 @@ const transformPostTitle: TransformBlock = ( block, innerBlocks ) => {
 	return createBlock(
 		'core/post-title',
 		{
-			__woocommerceNamespace:
-				'woocommerce/product-collection/product-title',
+			__woocommerceNamespace: PRODUCT_TITLE_VARIATION_NAME,
 			...restAttributes,
 		},
 		innerBlocks
@@ -93,8 +96,7 @@ const transformPostSummary: TransformBlock = ( block, innerBlocks ) => {
 	return createBlock(
 		'core/post-excerpt',
 		{
-			__woocommerceNamespace:
-				'woocommerce/product-collection/product-summary',
+			__woocommerceNamespace: PRODUCT_SUMMARY_VARIATION_NAME,
 			...restAttributes,
 		},
 		innerBlocks
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/constants.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/constants.ts
index 87f10879ebc..8a0f856bc6b 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/constants.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/constants.ts
@@ -17,6 +17,7 @@ import type { InnerBlockTemplate } from '@wordpress/blocks';
  * Internal dependencies
  */
 import blockJson from './block.json';
+import { PRODUCT_TITLE_VARIATION_NAME } from '../product-elements-blocks/variations/constants';
 import {
 	ProductCollectionAttributes,
 	ProductCollectionQuery,
@@ -25,7 +26,6 @@ import {
 } from './types';

 export const PRODUCT_COLLECTION_BLOCK_NAME = blockJson.name;
-const PRODUCT_TITLE_NAME = `${ PRODUCT_COLLECTION_BLOCK_NAME }/product-title`;

 export const STOCK_STATUS_OPTIONS = getSetting< Record< string, string > >(
 	'stockStatusOptions',
@@ -164,7 +164,7 @@ export const INNER_BLOCKS_PRODUCT_TEMPLATE: InnerBlockTemplate = [
 					},
 				},
 				isLink: true,
-				__woocommerceNamespace: PRODUCT_TITLE_NAME,
+				__woocommerceNamespace: PRODUCT_TITLE_VARIATION_NAME,
 			},
 		],
 		[
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/index.tsx
index c182d0a1dc3..9851a08df3d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/index.tsx
@@ -10,8 +10,6 @@ import metadata from './block.json';
 import edit from './edit';
 import save from './save';
 import icon from './icon';
-import registerProductSummaryVariation from './variations/elements/product-summary';
-import registerProductTitleVariation from './variations/elements/product-title';
 import registerCollections from './collections';
 import { addProductCollectionToQueryPaginationParentOrAncestor } from './utils';

@@ -20,7 +18,5 @@ registerBlockType( metadata, {
 	edit,
 	save,
 } );
-registerProductSummaryVariation();
-registerProductTitleVariation();
 registerCollections();
 addProductCollectionToQueryPaginationParentOrAncestor();
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/index.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/index.ts
deleted file mode 100644
index 9bb2335e783..00000000000
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/index.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * External dependencies
- */
-import type { Block } from '@wordpress/blocks';
-import { addFilter } from '@wordpress/hooks';
-
-/**
- * Internal dependencies
- */
-import { CORE_NAME as PRODUCT_TITLE_ID } from './elements/product-title';
-import { CORE_NAME as PRODUCT_SUMMARY_ID } from './elements/product-summary';
-
-const EXTENDED_CORE_ELEMENTS = [ PRODUCT_SUMMARY_ID, PRODUCT_TITLE_ID ];
-
-function registerProductCollectionElementsNamespace(
-	props: Block,
-	blockName: string
-) {
-	if ( EXTENDED_CORE_ELEMENTS.includes( blockName ) ) {
-		// Gracefully handle if settings.attributes is undefined.
-		// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-		// @ts-ignore -- We need this because `attributes` is marked as `readonly`
-		props.attributes = {
-			...props.attributes,
-			__woocommerceNamespace: {
-				type: 'string',
-			},
-		};
-	}
-
-	return props;
-}
-
-addFilter(
-	'blocks.registerBlockType',
-	'core/custom-class-name/attribute',
-	registerProductCollectionElementsNamespace
-);
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/summary/upgrade.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/summary/upgrade.tsx
index 3d6c072fe46..9ca637e98fd 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/summary/upgrade.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/summary/upgrade.tsx
@@ -15,21 +15,22 @@ import {
 	type ComponentType,
 } from '@wordpress/element';
 import { type EditorBlock } from '@woocommerce/types';
-import { VARIATION_NAME as PQ_PRODUCT_SUMMARY_VARIATION_NAME } from '@woocommerce/blocks/product-query/variations/elements/product-summary';
-import { VARIATION_NAME as PC_PRODUCT_SUMMARY_VARIATION_NAME } from '@woocommerce/blocks/product-collection/variations/elements/product-summary';
 import { UpgradeDowngradeNotice } from '@woocommerce/editor-components/upgrade-downgrade-notice';

+/**
+ * Internal dependencies
+ */
+import { PRODUCT_SUMMARY_NAMESPACES } from '../variations/constants';
+
 const CORE_NAME = 'core/post-excerpt';

 const isProductSummaryBlockVariation = ( props: BlockInstance ) => {
-	const pqVariation =
-		props.attributes.__woocommerceNamespace ===
-		PQ_PRODUCT_SUMMARY_VARIATION_NAME;
-	const pcVariation =
-		props.attributes.__woocommerceNamespace ===
-		PC_PRODUCT_SUMMARY_VARIATION_NAME;
-
-	return props.name === CORE_NAME && ( pqVariation || pcVariation );
+	return (
+		props.name === CORE_NAME &&
+		PRODUCT_SUMMARY_NAMESPACES.includes(
+			props.attributes.__woocommerceNamespace
+		)
+	);
 };

 const UpgradeNotice = ( { clientId }: { clientId: string } ) => {
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/constants.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/constants.ts
new file mode 100644
index 00000000000..54cad55025d
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/constants.ts
@@ -0,0 +1,24 @@
+export const PRODUCT_TITLE_VARIATION_NAME = 'woocommerce/product-title';
+export const PRODUCT_SUMMARY_VARIATION_NAME = 'woocommerce/product-summary';
+
+const DEPRECATED_PRODUCT_QUERY_PRODUCT_TITLE_VARIATION_NAME =
+	'woocommerce/product-query/product-title';
+const DEPRECATED_PRODUCT_QUERY_PRODUCT_SUMMARY_VARIATION_NAME =
+	'woocommerce/product-query/product-summary';
+
+const DEPRECATED_PRODUCT_COLLECTION_PRODUCT_TITLE_VARIATION_NAME =
+	'woocommerce/product-collection/product-title';
+const DEPRECATED_PRODUCT_COLLECTION_PRODUCT_SUMMARY_VARIATION_NAME =
+	'woocommerce/product-collection/product-summary';
+
+export const PRODUCT_TITLE_NAMESPACES = [
+	PRODUCT_TITLE_VARIATION_NAME,
+	DEPRECATED_PRODUCT_QUERY_PRODUCT_TITLE_VARIATION_NAME,
+	DEPRECATED_PRODUCT_COLLECTION_PRODUCT_TITLE_VARIATION_NAME,
+];
+
+export const PRODUCT_SUMMARY_NAMESPACES = [
+	PRODUCT_SUMMARY_VARIATION_NAME,
+	DEPRECATED_PRODUCT_QUERY_PRODUCT_SUMMARY_VARIATION_NAME,
+	DEPRECATED_PRODUCT_COLLECTION_PRODUCT_SUMMARY_VARIATION_NAME,
+];
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/index.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/index.ts
new file mode 100644
index 00000000000..1ddf9a5d01c
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/index.ts
@@ -0,0 +1,34 @@
+/**
+ * External dependencies
+ */
+import type { Block } from '@wordpress/blocks';
+import { addFilter } from '@wordpress/hooks';
+
+/**
+ * Internal dependencies
+ */
+import registerProductTitleVariation from './product-title';
+import registerProductSummaryVariation from './product-summary';
+
+addFilter(
+	'blocks.registerBlockType',
+	'woocommerce/product-elements/namespace',
+	( settings: Block, blockName: string ) => {
+		if (
+			! [ 'core/post-title', 'core/post-excerpt' ].includes( blockName )
+		) {
+			return settings;
+		}
+
+		return {
+			...settings,
+			attributes: {
+				...settings.attributes,
+				__woocommerceNamespace: { type: 'string' },
+			},
+		};
+	}
+);
+
+registerProductTitleVariation();
+registerProductSummaryVariation();
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/product-summary.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/product-summary.tsx
similarity index 67%
rename from plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/product-summary.tsx
rename to plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/product-summary.tsx
index 6695593ee03..9b729345358 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/product-summary.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/product-summary.tsx
@@ -8,21 +8,21 @@ import { page } from '@wordpress/icons';
  * Internal dependencies
  */
 import { registerElementVariation } from './utils';
+import { BLOCK_DESCRIPTION, BLOCK_TITLE } from '../summary/constants';
 import {
-	BLOCK_DESCRIPTION,
-	BLOCK_TITLE,
-} from '../../../product-elements-blocks/summary/constants';
-import blockJson from '../../block.json';
+	PRODUCT_SUMMARY_VARIATION_NAME,
+	PRODUCT_SUMMARY_NAMESPACES,
+} from './constants';

 export const CORE_NAME = 'core/post-excerpt';
-export const VARIATION_NAME = `${ blockJson.name }/product-summary`;

 const registerProductSummary = () => {
 	registerElementVariation( CORE_NAME, {
 		blockDescription: BLOCK_DESCRIPTION,
 		blockIcon: <Icon icon={ page } />,
 		blockTitle: BLOCK_TITLE,
-		variationName: VARIATION_NAME,
+		variationName: PRODUCT_SUMMARY_VARIATION_NAME,
+		namespaces: PRODUCT_SUMMARY_NAMESPACES,
 		scope: [],
 	} );
 };
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/product-title.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/product-title.tsx
similarity index 77%
rename from plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/product-title.tsx
rename to plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/product-title.tsx
index 12306b8f2dc..350ace54be3 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/product-title.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/product-title.tsx
@@ -9,10 +9,12 @@ import { __ } from '@wordpress/i18n';
  * Internal dependencies
  */
 import { registerElementVariation } from './utils';
-import blockJson from '../../block.json';
+import {
+	PRODUCT_TITLE_VARIATION_NAME,
+	PRODUCT_TITLE_NAMESPACES,
+} from './constants';

 export const CORE_NAME = 'core/post-title';
-export const VARIATION_NAME = `${ blockJson.name }/product-title`;

 const registerProductTitle = () => {
 	registerElementVariation( CORE_NAME, {
@@ -22,7 +24,9 @@ const registerProductTitle = () => {
 		),
 		blockIcon: <Icon icon={ heading } />,
 		blockTitle: __( 'Product Title', 'woocommerce' ),
-		variationName: VARIATION_NAME,
+		variationName: PRODUCT_TITLE_VARIATION_NAME,
+		namespaces: PRODUCT_TITLE_NAMESPACES,
+
 		scope: [ 'block', 'inserter' ],
 	} );
 };
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/utils.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/utils.tsx
similarity index 78%
rename from plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/utils.tsx
rename to plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/utils.tsx
index 6e5b5aa44d1..47788d3a4f1 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/variations/elements/utils.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-elements-blocks/variations/utils.tsx
@@ -5,13 +5,15 @@ import {
 	type BlockVariationScope,
 	registerBlockVariation,
 } from '@wordpress/blocks';
+import type { ReactElement } from '@wordpress/element';

 interface VariationDetails {
 	blockDescription: string;
-	blockIcon: JSX.Element;
+	blockIcon: ReactElement;
 	blockTitle: string;
 	variationName: string;
 	scope: BlockVariationScope[];
+	namespaces: string[];
 }

 export function registerElementVariation(
@@ -22,6 +24,7 @@ export function registerElementVariation(
 		blockTitle,
 		variationName,
 		scope,
+		namespaces,
 	}: VariationDetails
 ) {
 	registerBlockVariation( coreName, {
@@ -29,7 +32,7 @@ export function registerElementVariation(
 		name: variationName,
 		title: blockTitle,
 		isActive: ( blockAttributes ) =>
-			blockAttributes.__woocommerceNamespace === variationName,
+			namespaces.includes( blockAttributes.__woocommerceNamespace ),
 		icon: {
 			src: blockIcon,
 		},
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/constants.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/constants.ts
index c579c76fedc..5b5e25c358d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/constants.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/constants.ts
@@ -9,7 +9,7 @@ import { isBoolean } from '@woocommerce/types';
  * Internal dependencies
  */
 import { QueryBlockAttributes } from './types';
-import { VARIATION_NAME as PRODUCT_TITLE_ID } from './variations/elements/product-title';
+import { PRODUCT_TITLE_VARIATION_NAME } from '../product-elements-blocks/variations/constants';
 import { VARIATION_NAME as PRODUCT_TEMPLATE_ID } from './variations/elements/product-template';

 export const PRODUCT_QUERY_VARIATION_NAME = 'woocommerce/product-query';
@@ -116,7 +116,7 @@ export const INNER_BLOCKS_TEMPLATE: InnerBlockTemplate[] = [
 						},
 					},
 					isLink: true,
-					__woocommerceNamespace: PRODUCT_TITLE_ID,
+					__woocommerceNamespace: PRODUCT_TITLE_VARIATION_NAME,
 				},
 			],
 			[
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/index.tsx
index 03c59763e18..78e1fd4e33d 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/index.tsx
@@ -7,25 +7,17 @@ import { addFilter } from '@wordpress/hooks';
 /**
  * Internal dependencies
  */
-import { CORE_NAME as PRODUCT_TITLE_ID } from './variations/elements/product-title';
-import { CORE_NAME as PRODUCT_SUMMARY_ID } from './variations/elements/product-summary';
 import { CORE_NAME as PRODUCT_TEMPLATE_ID } from './variations/elements/product-template';
 import './inspector-controls';
 import './style.scss';
 import './variations/product-query';
 import './variations/related-products';

-const EXTENDED_CORE_ELEMENTS = [
-	PRODUCT_SUMMARY_ID,
-	PRODUCT_TEMPLATE_ID,
-	PRODUCT_TITLE_ID,
-];
-
 function registerProductQueryElementsNamespace(
 	props: Block,
 	blockName: string
 ) {
-	if ( EXTENDED_CORE_ELEMENTS.includes( blockName ) ) {
+	if ( PRODUCT_TEMPLATE_ID === blockName ) {
 		// Gracefully handle if settings.attributes is undefined.
 		// eslint-disable-next-line @typescript-eslint/ban-ts-comment
 		// @ts-ignore -- We need this because `attributes` is marked as `readonly`
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/elements/product-summary.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/elements/product-summary.tsx
deleted file mode 100644
index 315007c3c2e..00000000000
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/elements/product-summary.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * External dependencies
- */
-import { Icon } from '@wordpress/components';
-import { page } from '@wordpress/icons';
-import {
-	BLOCK_DESCRIPTION,
-	BLOCK_TITLE,
-} from '@woocommerce/product-elements/summary/constants';
-
-/**
- * Internal dependencies
- */
-import { registerElementVariation } from './utils';
-
-export const CORE_NAME = 'core/post-excerpt';
-export const VARIATION_NAME = 'woocommerce/product-query/product-summary';
-
-registerElementVariation( CORE_NAME, {
-	blockDescription: BLOCK_DESCRIPTION,
-	blockIcon: <Icon icon={ page } />,
-	blockTitle: BLOCK_TITLE,
-	variationName: VARIATION_NAME,
-	scope: [],
-} );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/elements/product-title.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/elements/product-title.tsx
deleted file mode 100644
index fc688de144d..00000000000
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/elements/product-title.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * External dependencies
- */
-import { Icon } from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
-import { heading } from '@wordpress/icons';
-
-/**
- * Internal dependencies
- */
-import { registerElementVariation } from './utils';
-
-export const CORE_NAME = 'core/post-title';
-export const VARIATION_NAME = 'woocommerce/product-query/product-title';
-
-registerElementVariation( CORE_NAME, {
-	blockDescription: __( 'Display the title of a product.', 'woocommerce' ),
-	blockIcon: <Icon icon={ heading } />,
-	blockTitle: __( 'Product Title', 'woocommerce' ),
-	variationName: VARIATION_NAME,
-	scope: [ 'block' ],
-} );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/related-products.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/related-products.tsx
index 6752739398f..65143038f90 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/related-products.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-query/variations/related-products.tsx
@@ -15,7 +15,7 @@ import { isBoolean } from '@woocommerce/types';
 import { QUERY_LOOP_ID } from '../constants';

 import { VARIATION_NAME as PRODUCT_TEMPLATE_ID } from './elements/product-template';
-import { VARIATION_NAME as PRODUCT_TITLE_ID } from './elements/product-title';
+import { PRODUCT_TITLE_VARIATION_NAME } from '../../product-elements-blocks/variations/constants';

 const VARIATION_NAME = 'woocommerce/related-products';

@@ -87,7 +87,7 @@ export const INNER_BLOCKS_TEMPLATE: InnerBlockTemplate[] = [
 					level: 3,
 					fontSize: 'medium',
 					isLink: true,
-					__woocommerceNamespace: PRODUCT_TITLE_ID,
+					__woocommerceNamespace: PRODUCT_TITLE_VARIATION_NAME,
 				},
 				[],
 			],
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/constants.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/constants.tsx
index 82f284c270c..e1f28dbc80e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/constants.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/constants.tsx
@@ -10,7 +10,7 @@ import type { InnerBlockTemplate } from '@wordpress/blocks';
  * Internal dependencies
  */
 import metadata from './block.json';
-import { VARIATION_NAME as PRODUCT_TITLE_VARIATION_NAME } from '../product-query/variations/elements/product-title';
+import { PRODUCT_TITLE_VARIATION_NAME } from '../product-elements-blocks/variations/constants';

 export const BLOCK_ICON = (
 	<Icon
diff --git a/plugins/woocommerce/client/blocks/assets/js/index.js b/plugins/woocommerce/client/blocks/assets/js/index.js
index 28ae6325465..fdf74a7f893 100644
--- a/plugins/woocommerce/client/blocks/assets/js/index.js
+++ b/plugins/woocommerce/client/blocks/assets/js/index.js
@@ -8,3 +8,4 @@ import './filters/get-block-attributes';
 import './filters/hide-incompatible-template-parts';
 import './filters/unregister-block-types';
 import './base/components/notice-banner/style.scss';
+import './blocks/product-elements-blocks/variations';
diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index b6aa1f6bfbc..30e4de9b0c5 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -3204,7 +3204,7 @@ EOT;
 <!-- wp:woocommerce/product-sale-badge {"align":"right"} /-->
 <!-- /wp:woocommerce/product-image -->

-<!-- wp:post-title {"textAlign":"center","isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}},"typography":{"lineHeight":"1.4"}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
+<!-- wp:post-title {"textAlign":"center","isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}},"typography":{"lineHeight":"1.4"}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-title"} /-->

 <!-- wp:woocommerce/product-price {"textAlign":"center","fontSize":"small"} /-->

diff --git a/plugins/woocommerce/patterns/related-products.php b/plugins/woocommerce/patterns/related-products.php
index 5bdef6a1178..fa3a8919e97 100644
--- a/plugins/woocommerce/patterns/related-products.php
+++ b/plugins/woocommerce/patterns/related-products.php
@@ -24,7 +24,7 @@
 			<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail"} -->
 				<!-- wp:woocommerce/product-sale-badge {"align":"right"} /-->
 			<!-- /wp:woocommerce/product-image -->
-			<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title"} /-->
+			<!-- wp:post-title {"textAlign":"center","level":3,"isLink":true,"style":{"spacing":{"margin":{"bottom":"0.75rem","top":"0"}}},"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-title"} /-->
 			<!-- wp:woocommerce/product-price {"textAlign":"center","fontSize":"small"} /-->
 			<!-- wp:woocommerce/product-button {"textAlign":"center","fontSize":"small"} /-->
 		<!-- /wp:woocommerce/product-template -->
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/Controller.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/Controller.php
index 9b7ee4f01fe..9f965dbc9b5 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/Controller.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCollection/Controller.php
@@ -96,12 +96,22 @@ class Controller extends AbstractBlock {
 	 * @return string   Modified block content with added interactivity.
 	 */
 	public function add_product_title_click_event_directives( $block_content, $block, $instance ) {
-		$namespace              = $instance->attributes['__woocommerceNamespace'] ?? '';
-		$is_product_title_block = 'woocommerce/product-collection/product-title' === $namespace;
-		$is_link                = $instance->attributes['isLink'] ?? false;
+		$namespace                         = $instance->attributes['__woocommerceNamespace'] ?? '';
+		$is_product_collection_inner_block = $instance->context['isProductCollectionInnerBlock'] ?? false;
+		$is_product_title_block            = in_array(
+			$namespace,
+			array(
+				'woocommerce/product-title',
+				'woocommerce/product-query/product-title',
+				'woocommerce/product-collection/product-title',
+			),
+			true
+		);
+
+		$is_link = $instance->attributes['isLink'] ?? false;

-		// Only proceed if the block is a Product Title (Post Title variation) block.
-		if ( $is_product_title_block && $is_link ) {
+		// Only proceed if the block is a Product Title (Post Title variation) block and is part of a Product Collection inner block.
+		if ( $is_product_title_block && $is_link && $is_product_collection_inner_block ) {
 			$p = new \WP_HTML_Tag_Processor( $block_content );
 			$p->next_tag( array( 'class_name' => 'wp-block-post-title' ) );
 			$is_anchor = $p->next_tag( array( 'tag_name' => 'a' ) );
diff --git a/plugins/woocommerce/templates/templates/blockified/archive-product.html b/plugins/woocommerce/templates/templates/blockified/archive-product.html
index 7f3d07e19d8..90c0c1f91fe 100644
--- a/plugins/woocommerce/templates/templates/blockified/archive-product.html
+++ b/plugins/woocommerce/templates/templates/blockified/archive-product.html
@@ -22,7 +22,7 @@
 		<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail"} -->
 			<!-- wp:woocommerce/product-sale-badge {"align":"right"} /-->
 		<!-- /wp:woocommerce/product-image -->
-		<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
+		<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
 		<!-- wp:woocommerce/product-price {"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
 		<!-- wp:woocommerce/product-button {"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
 		<!-- /wp:woocommerce/product-template -->
diff --git a/plugins/woocommerce/templates/templates/blockified/product-search-results.html b/plugins/woocommerce/templates/templates/blockified/product-search-results.html
index d77d935d5e3..1f83b855707 100644
--- a/plugins/woocommerce/templates/templates/blockified/product-search-results.html
+++ b/plugins/woocommerce/templates/templates/blockified/product-search-results.html
@@ -20,7 +20,7 @@
 		<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail"} -->
 			<!-- wp:woocommerce/product-sale-badge {"align":"right"} /-->
 		<!-- /wp:woocommerce/product-image -->
-		<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
+		<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
 		<!-- wp:woocommerce/product-price {"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
 		<!-- wp:woocommerce/product-button {"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
 		<!-- /wp:woocommerce/product-template -->
diff --git a/plugins/woocommerce/templates/templates/blockified/single-product.html b/plugins/woocommerce/templates/templates/blockified/single-product.html
index d448e93ca26..f5f2989cb6b 100644
--- a/plugins/woocommerce/templates/templates/blockified/single-product.html
+++ b/plugins/woocommerce/templates/templates/blockified/single-product.html
@@ -15,13 +15,13 @@

 		<!-- wp:column -->
 		<div class="wp-block-column">
-			<!-- wp:post-title {"level": 1, "__woocommerceNamespace":"woocommerce/product-query/product-title"} /-->
+			<!-- wp:post-title {"level": 1, "__woocommerceNamespace":"woocommerce/product-title"} /-->

 			<!-- wp:woocommerce/product-rating /-->

 			<!-- wp:woocommerce/product-price {"fontSize":"large"} /-->

-			<!-- wp:post-excerpt {"__woocommerceNamespace":"woocommerce/product-query/product-summary", "excerptLength":100} /-->
+			<!-- wp:post-excerpt {"__woocommerceNamespace":"woocommerce/product-summary", "excerptLength":100} /-->

 			<!-- wp:woocommerce/add-to-cart-form /-->

@@ -50,4 +50,4 @@
 </main>
 <!-- /wp:group -->

-<!-- wp:template-part {"slug":"footer"} /-->
+<!-- wp:template-part {"slug":"footer"} /-->
\ No newline at end of file
diff --git a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html
index d7907acdc4e..fd6255c6591 100644
--- a/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html
+++ b/plugins/woocommerce/templates/templates/blockified/taxonomy-product_attribute.html
@@ -24,7 +24,7 @@
 		<!-- wp:woocommerce/product-image {"showSaleBadge":false,"imageSizing":"thumbnail"} -->
 			<!-- wp:woocommerce/product-sale-badge {"align":"right"} /-->
 		<!-- /wp:woocommerce/product-image -->
-		<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-collection/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
+		<!-- wp:post-title {"textAlign":"center","level":2,"isLink":true,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-title","style":{"typography":{"lineHeight":"1.4"}}} /-->
 		<!-- wp:woocommerce/product-price {"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
 		<!-- wp:woocommerce/product-button {"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
 		<!-- /wp:woocommerce/product-template -->
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/ControllerTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/ControllerTest.php
index a2ab3fd2141..5364fb54dde 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/ControllerTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductCollection/ControllerTest.php
@@ -6,6 +6,7 @@ namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes\ProductCollection;

 use Automattic\WooCommerce\Blocks\Assets\Api;
 use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Controller;
+use Automattic\WooCommerce\Blocks\BlockTypes\ProductCollection\Renderer;
 use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
 use Automattic\WooCommerce\Blocks\Package;
 use Automattic\WooCommerce\Tests\Blocks\Mocks\AssetDataRegistryMock;
@@ -35,17 +36,10 @@ class ControllerTest extends WC_Unit_Test_Case {

 		$this->sut = new class( $asset_api, $asset_data_registry, $integration_registry ) extends Controller {
 			/**
-			 * Skip normal hook registration for unit tests.
+			 * Initialize the renderer without registering the block again.
 			 */
-			protected function initialize() {
-				$this->renderer = new class() {
-					/**
-					 * Accept parsed block data from the controller under test.
-					 *
-					 * @param array $parsed_block The parsed block.
-					 */
-					public function set_parsed_block( array $parsed_block ): void {}
-				};
+			protected function initialize(): void {
+				$this->renderer = new Renderer();
 			}
 		};

@@ -150,20 +144,34 @@ class ControllerTest extends WC_Unit_Test_Case {
 	}

 	/**
-	 * Create a real Post Title block instance for the public render filter.
+	 * Create a Post Title block with context from rendering its parent Product Collection.
 	 *
 	 * @param array $attributes Block attributes.
 	 * @return \WP_Block
 	 */
 	private function create_post_title_block( array $attributes ): \WP_Block {
-		return new \WP_Block(
+		$product_collection = new \WP_Block(
 			array(
-				'blockName'    => 'core/post-title',
-				'attrs'        => $attributes,
-				'innerBlocks'  => array(),
+				'blockName'    => 'woocommerce/product-collection',
+				'attrs'        => array(),
+				'innerBlocks'  => array(
+					array(
+						'blockName'    => 'core/post-title',
+						'attrs'        => $attributes,
+						'innerBlocks'  => array(),
+						'innerHTML'    => '',
+						'innerContent' => array(),
+					),
+				),
 				'innerHTML'    => '',
-				'innerContent' => array(),
+				'innerContent' => array( null ),
 			)
 		);
+		$product_collection->render( array( 'dynamic' => false ) );
+
+		/** @var \WP_Block $instance */
+		$instance = $product_collection->inner_blocks[0];
+
+		return $instance;
 	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php
index 5f32b925fa2..32f1ed9459d 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/SingleProduct.php
@@ -268,7 +268,7 @@ class SingleProduct extends \WP_UnitTestCase {

 <!-- wp:column -->
 <div class="wp-block-column">
-<!-- wp:post-title {"isLink":true,"__woocommerceNamespace":"woocommerce/product-query/product-title"} /-->
+<!-- wp:post-title {"isLink":true,"__woocommerceNamespace":"woocommerce/product-title"} /-->

 <!-- wp:woocommerce/product-rating /-->

diff --git a/plugins/woocommerce/tests/php/src/Blocks/Templates/SingleProductTemplateTests.php b/plugins/woocommerce/tests/php/src/Blocks/Templates/SingleProductTemplateTests.php
index 343d033f3a8..95f543d92b2 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Templates/SingleProductTemplateTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Templates/SingleProductTemplateTests.php
@@ -433,13 +433,13 @@ class SingleProductTemplateTests extends WP_UnitTestCase {

 				<!-- wp:column -->
 				<div class="wp-block-column">
-					<!-- wp:post-title {"level": 1, "__woocommerceNamespace":"woocommerce/product-query/product-title"} /-->
+					<!-- wp:post-title {"level": 1, "__woocommerceNamespace":"woocommerce/product-title"} /-->

 					<!-- wp:woocommerce/product-rating /-->

 					<!-- wp:woocommerce/product-price {"fontSize":"large"} /-->

-					<!-- wp:post-excerpt {"__woocommerceNamespace":"woocommerce/product-query/product-summary", "excerptLength":100} /-->
+					<!-- wp:post-excerpt {"__woocommerceNamespace":"woocommerce/product-summary", "excerptLength":100} /-->

 					<!-- wp:woocommerce/add-to-cart-form /-->