Repository: Automattic/wp-calypso
Pull request #103041: Experiment streamlining the plans grid + checkout: Plans grid changes
Author: login: claudiucelfilip
Created: 2025-05-01T11:34:48Z | Merged: 2025-05-06T06:32:01Z

<!--
Link a related issue to this PR. If the PR does not immediately resolve the issue,
for example, it requires a separate deployment to production, avoid
using the "fixes" keyword and instead attach the [Status] Fix Inbound label to
the linked issue.
-->

Related to p58i-kUT-p2

## Proposed Changes

* Default to 1Y or 3Y plan prices for the corresponding variations
* Show term saving labels on top of the prices
* Show streamlined billing interval descriptions 

<img width="1825" alt="Screenshot 2025-05-01 at 13 49 26" src="https://github.com/user-attachments/assets/72ff3243-1aa9-4efe-a618-d21971e861cd" />


## Why are these changes being made?
<!--
It's easy to see what a PR does but much harder to find out why it was made,
particularly when researching old changes in history. Record an explanation of
the motivation behind this change and how it will help.
-->

We're experimenting with streamlining the plans and checkout user experience.

## Testing Instructions

<!--
Add as many details as possible to help others reproduce the issue and test the fix.
"Before / After" screenshots can also be very helpful when the change is visual.
-->

#### Control (unassigned)
* Go to `/setup/onboarding/plans`
* There should be no changes
<img width="1533" alt="Screenshot 2025-05-01 at 14 06 02" src="https://github.com/user-attachments/assets/5f20f2a0-64fa-4fee-b510-3cabb639ea0f" />


#### Control (plans_control_checkout_radio variation)
* Go to the `plans_control_checkout_radio ` experiment and assign yourself the `plans_1y_checkout_dropdown`. You might need to remove the `explat-experiment--calypso_streamlined_plans_checkout` entry from the localStorage to fetch the new assignment. 
* There should be no changes

#### 1Y plans
* Go to the `calypso_streamlined_plans_checkout` experiment and assign yourself the `plans_1y_checkout_dropdown`.
* You should see the 1Y plan version of the page
  * interval toggle should be hidden
  * prices should match the design
  * term saving label should be shown
  * the billing term description copy should be the same as in the design.
<img width="1627" alt="Screenshot 2025-05-01 at 14 03 00" src="https://github.com/user-attachments/assets/0ae5811b-d2c3-4e01-a561-a93f572813ec" />

#### 3Y plans
* Switch to the `plans_3y_checkout_control` variation.
* You should see the 3Y plan version of the page
<img width="1450" alt="Screenshot 2025-05-01 at 14 04 57" src="https://github.com/user-attachments/assets/8dd83915-51b9-438b-aee0-4f0770add084" />


#### 2Y plans (German)
* Set proxy/VPN to navigate from Germany
* You should see the 2Y plan version of the page

<img width="1677" alt="Screenshot 2025-05-01 at 14 16 58" src="https://github.com/user-attachments/assets/348b2bb4-6a6b-4d0e-87a5-f9ec7031f4c6" />



## Pre-merge Checklist

<!--
Complete applicable items on this checklist **before** merging into trunk. Inapplicable items can be left unchecked.

Both the PR author and reviewer are responsible for ensuring the checklist is completed.
-->

- [ ] Has the general commit checklist been followed? (PCYsg-hS-p2)
- [ ] [Have you written new tests](https://wpcalypso.wordpress.com/devdocs/docs/testing/index.md) for your changes?
- [ ] Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
- [ ] Have you checked for TypeScript, React or other console errors?
- [ ] Have you used memoizing on expensive computations? More info in [Memoizing with create-selector](https://github.com/Automattic/wp-calypso/blob/trunk/packages/state-utils/src/create-selector/README.md) and [Using memoizing selectors](https://react-redux.js.org/api/hooks#using-memoizing-selectors) and [Our Approach to Data](https://github.com/Automattic/wp-calypso/blob/trunk/docs/our-approach-to-data.md)
- [ ] Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    - [ ] For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages. 
- [ ] For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?



Diff:
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/unified-plans/unified-plans-step.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/unified-plans/unified-plans-step.tsx
index 53ce0d271cbc..4cb9570b54b2 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/unified-plans/unified-plans-step.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/unified-plans/unified-plans-step.tsx
@@ -27,6 +27,11 @@ import { SIGNUP_DOMAIN_ORIGIN } from 'calypso/lib/analytics/signup';
 import { triggerGuidesForStep } from 'calypso/lib/guides/trigger-guides-for-step';
 import { buildUpgradeFunction } from 'calypso/lib/signup/step-actions';
 import PlansFeaturesMain from 'calypso/my-sites/plans-features-main';
+import { useFCCARestrictions } from 'calypso/my-sites/plans-features-main/hooks/use-fcca-restrictions';
+import {
+	useStreamlinedPriceExperiment,
+	isStreamlinedPricePlansTreatment,
+} from 'calypso/my-sites/plans-features-main/hooks/use-streamlined-price-experiment';
 import { getStepUrl } from 'calypso/signup/utils';
 import { getDomainFromUrl } from 'calypso/site-profiler/utils/get-valid-url';
 import { useDispatch as reduxUseDispatch, useSelector } from 'calypso/state';
@@ -223,6 +228,8 @@ function UnifiedPlansStep( {
 	const initializedSitesBackUrl = useSelector( ( state ) =>
 		getCurrentUserSiteCount( state ) ? '/sites/' : null
 	);
+	const [ isStreamlinedPriceExperimentLoading, streamlinedPriceExperimentAssignment ] =
+		useStreamlinedPriceExperiment();
 
 	useSiteGlobalStylesOnPersonal();
 
@@ -444,7 +451,18 @@ function UnifiedPlansStep( {
 		}
 	}
 
-	const intervalTypeValue = intervalType || getIntervalType( path );
+	const { shouldRestrict3YearPlans } = useFCCARestrictions();
+	let defaultIntervalType = 'yearly';
+	const experimentValue = streamlinedPriceExperimentAssignment as string;
+	if (
+		! isStreamlinedPriceExperimentLoading &&
+		isStreamlinedPricePlansTreatment( experimentValue ) &&
+		experimentValue.startsWith( 'plans_3y' )
+	) {
+		defaultIntervalType = shouldRestrict3YearPlans() ? '2yearly' : '3yearly';
+	}
+
+	const intervalTypeValue = intervalType || getIntervalType( path, defaultIntervalType );
 
 	let paidDomainName = domainItem?.meta;
 
diff --git a/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts b/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts
index 2578d35d3b20..80f3eb33c12c 100644
--- a/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts
+++ b/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts
@@ -2,12 +2,19 @@ import { useExperiment, loadExperimentAssignment } from 'calypso/lib/explat';
 
 const STREAMLINED_PRICE_EXPERIMENT_NAME = 'calypso_streamlined_plans_checkout';
 
-export function useStreamlinedPriceExperiment() {
+export function useStreamlinedPriceExperiment(): [ boolean, string | null ] {
 	const [ isLoading, assignment ] = useExperiment( STREAMLINED_PRICE_EXPERIMENT_NAME );
-	return [ isLoading, assignment?.variationName ];
+	return [ isLoading, assignment?.variationName ?? null ];
 }
 
 export async function isStreamlinedPriceExperiment() {
 	const assignment = await loadExperimentAssignment( STREAMLINED_PRICE_EXPERIMENT_NAME );
 	return assignment?.variationName !== null;
 }
+
+export function isStreamlinedPricePlansTreatment( variationName?: string | null ) {
+	if ( ! variationName ) {
+		return false;
+	}
+	return ! variationName.includes( 'plans_control' );
+}
diff --git a/client/my-sites/plans-features-main/index.tsx b/client/my-sites/plans-features-main/index.tsx
index 99ee92d5f81c..7fc4a5208e3b 100644
--- a/client/my-sites/plans-features-main/index.tsx
+++ b/client/my-sites/plans-features-main/index.tsx
@@ -80,7 +80,10 @@ import useGenerateActionHook from './hooks/use-generate-action-hook';
 import usePlanFromUpsells from './hooks/use-plan-from-upsells';
 import usePlanIntentFromSiteMeta from './hooks/use-plan-intent-from-site-meta';
 import useSelectedFeature from './hooks/use-selected-feature';
-import { useStreamlinedPriceExperiment } from './hooks/use-streamlined-price-experiment';
+import {
+	isStreamlinedPricePlansTreatment,
+	useStreamlinedPriceExperiment,
+} from './hooks/use-streamlined-price-experiment';
 import useGetFreeSubdomainSuggestion from './hooks/use-suggested-free-domain-from-paid-domain';
 import type {
 	PlansIntent,
@@ -469,15 +472,23 @@ const PlansFeaturesMain = ( {
 	const [ isStreamlinedPriceExperimentLoading, streamlinedPriceExperimentAssignment ] =
 		useStreamlinedPriceExperiment();
 
-	let hidePlanSelector = false;
+	const showStreamlinedPriceExperiment =
+		isInSignup && isStreamlinedPricePlansTreatment( streamlinedPriceExperimentAssignment );
+
+	let hidePlanSelector = true;
+	let enableTermSavingsPriceDisplay = true;
 	// In the "purchase a plan and free domain" flow we do not want to show
 	// monthly plans because monthly plans do not come with a free domain.
+	// We are also hiding the plan interval selector and showing terms savings prices
+	// for the compatible streamlined price experiment variations.
 	if (
-		redirectToAddDomainFlow !== undefined ||
-		hidePlanTypeSelector ||
-		( isInSignup && ! isStreamlinedPriceExperimentLoading && streamlinedPriceExperimentAssignment )
+		redirectToAddDomainFlow === undefined &&
+		! hidePlanTypeSelector &&
+		! isStreamlinedPriceExperimentLoading &&
+		! showStreamlinedPriceExperiment
 	) {
-		hidePlanSelector = true;
+		hidePlanSelector = false;
+		enableTermSavingsPriceDisplay = false;
 	}
 
 	let _customerType = chooseDefaultCustomerType( {
@@ -835,7 +846,7 @@ const PlansFeaturesMain = ( {
 							data-e2e-plans="wpcom"
 						>
 							<div className="plans-wrapper">
-								{ gridPlansForFeaturesGrid && (
+								{ gridPlansForFeaturesGrid && ! isStreamlinedPriceExperimentLoading && (
 									<FeaturesGrid
 										allFeaturesList={ getFeaturesList() }
 										className="plans-features-main__features-grid"
@@ -871,7 +882,8 @@ const PlansFeaturesMain = ( {
 										enableReducedFeatureGroupSpacing={ showSimplifiedFeatures }
 										enableLogosOnlyForEnterprisePlan={ showSimplifiedFeatures }
 										hideFeatureGroupTitles={ showSimplifiedFeatures }
-										enableTermSavingsPriceDisplay={ false }
+										enableTermSavingsPriceDisplay={ enableTermSavingsPriceDisplay }
+										showStreamlinedBillingDescription={ showStreamlinedPriceExperiment }
 									/>
 								) }
 								{ showEscapeHatch && hidePlansFeatureComparison && viewAllPlansButton }
@@ -900,42 +912,45 @@ const PlansFeaturesMain = ( {
 														coupon={ coupon }
 													/>
 												) }
-											{ gridPlansForComparisonGrid && gridPlansForPlanTypeSelector && (
-												<ComparisonGrid
-													allFeaturesList={ getFeaturesList() }
-													className="plans-features-main__comparison-grid"
-													coupon={ coupon }
-													currentSitePlanSlug={ sitePlanSlug }
-													gridPlans={ gridPlansForComparisonGrid }
-													hideUnavailableFeatures={ hideUnavailableFeatures }
-													intent={ intent }
-													intervalType={ intervalType }
-													isInAdmin={ ! isInSignup }
-													isInSiteDashboard={ isInSiteDashboard }
-													isInSignup={ isInSignup }
-													onStorageAddOnClick={ handleStorageAddOnClick }
-													planTypeSelectorProps={
-														! hidePlanSelector
-															? { ...planTypeSelectorProps, plans: gridPlansForPlanTypeSelector }
-															: undefined
-													}
-													recordTracksEvent={ recordTracksEvent }
-													reflectStorageSelectionInPlanPrices
-													selectedFeature={ selectedFeature }
-													selectedPlan={ selectedPlan }
-													showUpgradeableStorage={ showUpgradeableStorage }
-													siteId={ siteId }
-													stickyRowOffset={ comparisonGridStickyRowOffset }
-													showRefundPeriod={ isAnyHostingFlow( flowName ) }
-													useAction={ useAction }
-													useCheckPlanAvailabilityForPurchase={
-														useCheckPlanAvailabilityForPurchase
-													}
-													enableFeatureTooltips
-													featureGroupMap={ featureGroupMapForComparisonGrid }
-													enableTermSavingsPriceDisplay={ false }
-												/>
-											) }
+											{ gridPlansForComparisonGrid &&
+												gridPlansForPlanTypeSelector &&
+												! isStreamlinedPriceExperimentLoading && (
+													<ComparisonGrid
+														allFeaturesList={ getFeaturesList() }
+														className="plans-features-main__comparison-grid"
+														coupon={ coupon }
+														currentSitePlanSlug={ sitePlanSlug }
+														gridPlans={ gridPlansForComparisonGrid }
+														hideUnavailableFeatures={ hideUnavailableFeatures }
+														intent={ intent }
+														intervalType={ intervalType }
+														isInAdmin={ ! isInSignup }
+														isInSiteDashboard={ isInSiteDashboard }
+														isInSignup={ isInSignup }
+														onStorageAddOnClick={ handleStorageAddOnClick }
+														planTypeSelectorProps={
+															! hidePlanSelector
+																? { ...planTypeSelectorProps, plans: gridPlansForPlanTypeSelector }
+																: undefined
+														}
+														recordTracksEvent={ recordTracksEvent }
+														reflectStorageSelectionInPlanPrices
+														selectedFeature={ selectedFeature }
+														selectedPlan={ selectedPlan }
+														showUpgradeableStorage={ showUpgradeableStorage }
+														siteId={ siteId }
+														stickyRowOffset={ comparisonGridStickyRowOffset }
+														showRefundPeriod={ isAnyHostingFlow( flowName ) }
+														useAction={ useAction }
+														useCheckPlanAvailabilityForPurchase={
+															useCheckPlanAvailabilityForPurchase
+														}
+														enableFeatureTooltips
+														featureGroupMap={ featureGroupMapForComparisonGrid }
+														enableTermSavingsPriceDisplay={ enableTermSavingsPriceDisplay }
+														showStreamlinedBillingDescription={ showStreamlinedPriceExperiment }
+													/>
+												) }
 											<ComparisonGridToggle
 												onClick={ toggleShowPlansComparisonGrid }
 												label={ translate( 'Hide comparison' ) }
diff --git a/client/my-sites/plans-features-main/test/index.jsx b/client/my-sites/plans-features-main/test/index.jsx
index 8993b39c3547..b2cbd6e48afd 100644
--- a/client/my-sites/plans-features-main/test/index.jsx
+++ b/client/my-sites/plans-features-main/test/index.jsx
@@ -21,6 +21,10 @@ jest.mock( '../hooks/use-suggested-free-domain-from-paid-domain', () => () => (
 	wpcomFreeDomainSuggestion: { isLoading: false, result: { domain_name: 'suggestion.com' } },
 	invalidateDomainSuggestionCache: () => {},
 } ) );
+jest.mock( '../hooks/use-streamlined-price-experiment', () => ( {
+	useStreamlinedPriceExperiment: () => [ false, null ],
+	isStreamlinedPriceExperiment: () => Promise.resolve( false ),
+} ) );
 jest.mock( 'calypso/state/purchases/selectors', () => ( {
 	getByPurchaseId: jest.fn(),
 } ) );
diff --git a/packages/plans-grid-next/src/components/comparison-grid/index.tsx b/packages/plans-grid-next/src/components/comparison-grid/index.tsx
index c7bacaed4640..268fd4cb46eb 100644
--- a/packages/plans-grid-next/src/components/comparison-grid/index.tsx
+++ b/packages/plans-grid-next/src/components/comparison-grid/index.tsx
@@ -525,6 +525,7 @@ const ComparisonGridHeader = forwardRef< HTMLDivElement, ComparisonGridHeaderPro
 		);
 	}
 );
+ComparisonGridHeader.displayName = 'ComparisonGridHeader';
 
 const ComparisonGridFeatureGroupRowCell: React.FunctionComponent< {
 	feature?: FeatureObject;
@@ -1159,6 +1160,7 @@ const WrappedComparisonGrid = ( {
 	featureGroupMap,
 	enableTermSavingsPriceDisplay,
 	reflectStorageSelectionInPlanPrices,
+	showStreamlinedBillingDescription,
 	...otherProps
 }: ComparisonGridExternalProps ) => {
 	const gridContainerRef = useRef< HTMLDivElement >( null );
@@ -1206,6 +1208,7 @@ const WrappedComparisonGrid = ( {
 				hideUnsupportedFeatures={ hideUnsupportedFeatures }
 				enableTermSavingsPriceDisplay={ enableTermSavingsPriceDisplay }
 				reflectStorageSelectionInPlanPrices={ reflectStorageSelectionInPlanPrices }
+				showStreamlinedBillingDescription={ showStreamlinedBillingDescription }
 			>
 				<ComparisonGrid
 					intervalType={ intervalType }
diff --git a/packages/plans-grid-next/src/components/features-grid/index.tsx b/packages/plans-grid-next/src/components/features-grid/index.tsx
index bbd9294a78fa..2adf4bcf7e28 100644
--- a/packages/plans-grid-next/src/components/features-grid/index.tsx
+++ b/packages/plans-grid-next/src/components/features-grid/index.tsx
@@ -388,6 +388,7 @@ const WrappedFeaturesGrid = ( props: FeaturesGridExternalProps ) => {
 		hideFeatureGroupTitles,
 		enterpriseFeaturesList,
 		enableTermSavingsPriceDisplay,
+		showStreamlinedBillingDescription,
 	} = props;
 
 	const gridContainerRef = useRef< HTMLDivElement >( null );
@@ -445,6 +446,7 @@ const WrappedFeaturesGrid = ( props: FeaturesGridExternalProps ) => {
 				featureGroupMap={ featureGroupMap }
 				enterpriseFeaturesList={ enterpriseFeaturesList }
 				enableTermSavingsPriceDisplay={ enableTermSavingsPriceDisplay }
+				showStreamlinedBillingDescription={ showStreamlinedBillingDescription }
 			>
 				<FeaturesGrid { ...props } gridSize={ gridSize ?? undefined } />
 			</PlansGridContextProvider>
diff --git a/packages/plans-grid-next/src/grid-context.tsx b/packages/plans-grid-next/src/grid-context.tsx
index cd2eb3a177a6..0252f8ea0c40 100644
--- a/packages/plans-grid-next/src/grid-context.tsx
+++ b/packages/plans-grid-next/src/grid-context.tsx
@@ -31,6 +31,7 @@ interface PlansGridContext {
 	hideFeatureGroupTitles?: boolean;
 	enterpriseFeaturesList?: string[];
 	reflectStorageSelectionInPlanPrices?: boolean;
+	showStreamlinedBillingDescription?: boolean;
 }
 
 const PlansGridContext = createContext< PlansGridContext >( {} as PlansGridContext );
@@ -56,6 +57,7 @@ const PlansGridContextProvider = ( {
 	hideFeatureGroupTitles,
 	enterpriseFeaturesList,
 	reflectStorageSelectionInPlanPrices,
+	showStreamlinedBillingDescription,
 }: GridContextProps ) => {
 	const gridPlansIndex = gridPlans.reduce(
 		( acc, gridPlan ) => ( {
@@ -90,6 +92,7 @@ const PlansGridContextProvider = ( {
 				hideFeatureGroupTitles,
 				enterpriseFeaturesList,
 				reflectStorageSelectionInPlanPrices,
+				showStreamlinedBillingDescription,
 			} }
 		>
 			{ children }
diff --git a/packages/plans-grid-next/src/hooks/data-store/use-plan-billing-description.tsx b/packages/plans-grid-next/src/hooks/data-store/use-plan-billing-description.tsx
index 2d83f4d84237..c6eddd197cc2 100644
--- a/packages/plans-grid-next/src/hooks/data-store/use-plan-billing-description.tsx
+++ b/packages/plans-grid-next/src/hooks/data-store/use-plan-billing-description.tsx
@@ -35,7 +35,8 @@ export default function usePlanBillingDescription( {
 }: UsePlanBillingDescriptionProps ) {
 	const translate = useTranslate();
 	const { currencyCode, originalPrice, discountedPrice, billingPeriod, introOffer } = pricing || {};
-	const { reflectStorageSelectionInPlanPrices } = usePlansGridContext();
+	const { reflectStorageSelectionInPlanPrices, showStreamlinedBillingDescription } =
+		usePlansGridContext();
 	const yearlyVariantPlanSlug = getPlanSlugForTermVariant( planSlug, TERM_ANNUALLY );
 
 	const yearlyVariantPricing = Plans.usePricingMetaForGridPlans( {
@@ -283,6 +284,19 @@ export default function usePlanBillingDescription( {
 				}
 			);
 		}
+	} else if ( showStreamlinedBillingDescription ) {
+		// When streamlined price experiment is active, use simplified billing description
+		if ( PLAN_ANNUAL_PERIOD === billingPeriod ) {
+			return translate( 'per month, billed annually' );
+		}
+
+		if ( PLAN_BIENNIAL_PERIOD === billingPeriod ) {
+			return translate( 'per month, billed every 2 years' );
+		}
+
+		if ( PLAN_TRIENNIAL_PERIOD === billingPeriod ) {
+			return translate( 'per month, billed every 3 years' );
+		}
 	} else if ( originalPriceFullTermText ) {
 		if ( PLAN_ANNUAL_PERIOD === billingPeriod ) {
 			return translate( 'per month, %(rawPrice)s billed annually, excl. taxes', {
diff --git a/packages/plans-grid-next/src/types.ts b/packages/plans-grid-next/src/types.ts
index 0dfdf0eee2e1..95acab97b149 100644
--- a/packages/plans-grid-next/src/types.ts
+++ b/packages/plans-grid-next/src/types.ts
@@ -240,6 +240,11 @@ export type GridContextProps = {
 	 * calculating prices.
 	 */
 	reflectStorageSelectionInPlanPrices?: boolean;
+
+	/**
+	 * Enable streamlined billing description
+	 */
+	showStreamlinedBillingDescription?: boolean;
 };
 
 export type ComparisonGridExternalProps = Omit<


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR experiments with streamlining the plans grid and checkout user experience by introducing a new feature flag ("showStreamlinedBillingDescription") and adjusting pricing display logic accordingly. Key changes include:  
- Adding a new property in the types and grid context for streamlined billing description.  
- Updating the plan billing description hook to use simplified billing copy when the experiment flag is enabled.  
- Modifying the plans-features-main component to adjust visibility and pricing behavior based on the streamlined price experiment.

### Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| packages/plans-grid-next/src/types.ts | Adds new property for streamlined billing description. |
| packages/plans-grid-next/src/hooks/data-store/use-plan-billing-description.tsx | Updates billing description logic to conditionally use simplified copy. |
| packages/plans-grid-next/src/grid-context.tsx | Propagates the new flag through the grid context. |
| packages/plans-grid-next/src/components/features-grid/index.tsx | Forwards the new flag to grid components. |
| packages/plans-grid-next/src/components/comparison-grid/index.tsx | Passes the new flag to the comparison grid component. |
| client/my-sites/plans-features-main/index.tsx | Adjusts plan selector and term savings logic based on the streamlined price experiment. |
| client/landing/stepper/declarative-flow/internals/steps-repository/unified-plans/unified-plans-step.tsx | Incorporates the streamlined price experiment into interval type calculations. |
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**packages/plans-grid-next/src/hooks/data-store/use-plan-billing-description.tsx:286**
* Ensure that tests cover the new streamlined billing description logic for annual, biennial, and triannual billing periods to validate that the correct copy is displayed for each billing period.
```
else if ( showStreamlinedBillingDescription ) {
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T11:35:37Z
user: login: chriskmnds
body: Hey. I had a brief look. I think (could be wrong) this steps into the same issue we had with the term-savings experiment, where it may lead to conflicting discount/saving displays in the grid. You can read more about it here: https://github.com/Automattic/wp-calypso/pull/96631 (the core PR from that experiment had some relevant media as well: https://github.com/Automattic/wp-calypso/pull/96357)

The gist of it is there are plans that may be discounted through a promo offer (I think first-year currency discounts are also promo offers now). It creates ambiguity, but there was a bit to it that I don't recall precisely right now. The solution was the logic around `isAnyGridPlanDiscounted` / in https://github.com/Automattic/wp-calypso/pull/96631)


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T12:55:39Z
user: login: dlind1
body: Tested, LGTM!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T14:41:46Z

Review comments:
diff_hunk: @@ -469,15 +469,21 @@ const PlansFeaturesMain = ( {
 	const [ isStreamlinedPriceExperimentLoading, streamlinedPriceExperimentAssignment ] =
 		useStreamlinedPriceExperiment();
 
-	let hidePlanSelector = false;
+	const showStreamlinedPriceExperiment =
+		isInSignup && Boolean( streamlinedPriceExperimentAssignment );
+
+	let hidePlanSelector = true;
+	let enableTermSavingsPriceDisplay = true;
 	// In the "purchase a plan and free domain" flow we do not want to show
 	// monthly plans because monthly plans do not come with a free domain.
 	if (
-		redirectToAddDomainFlow !== undefined ||
-		hidePlanTypeSelector ||
-		( isInSignup && ! isStreamlinedPriceExperimentLoading && streamlinedPriceExperimentAssignment )
+		redirectToAddDomainFlow === undefined &&
+		! hidePlanTypeSelector &&
+		! isStreamlinedPriceExperimentLoading &&
+		! showStreamlinedPriceExperiment
path: client/my-sites/plans-features-main/index.tsx
user: login: Copilot
body: [nitpick] Review the boolean logic for 'hidePlanSelector' and 'enableTermSavingsPriceDisplay' in conjunction with the streamlined price experiment; consider adding inline comments or refactoring to improve clarity of how these values are determined.
created_at: 2025-05-01T11:35:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -469,15 +472,21 @@ const PlansFeaturesMain = ( {
 	const [ isStreamlinedPriceExperimentLoading, streamlinedPriceExperimentAssignment ] =
 		useStreamlinedPriceExperiment();
 
-	let hidePlanSelector = false;
+	const showStreamlinedPriceExperiment =
+		isInSignup && isStreamlinedPricePlansTreatment( streamlinedPriceExperimentAssignment );
+
+	let hidePlanSelector = true;
+	let enableTermSavingsPriceDisplay = true;
 	// In the "purchase a plan and free domain" flow we do not want to show
path: client/my-sites/plans-features-main/index.tsx
user: login: dlind1
body: Nit: Should this comment be updated?
created_at: 2025-05-05T14:19:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-01T11:35:00Z
body: <details>
<summary>Calypso Live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-142880">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-142880%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-142880">https://calypso.live?image=registry.a8c.com/calypso/app:build-142880</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Jetpack Cloud live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-142880&env=jetpack">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-142880%26env%3Djetpack%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-142880&env=jetpack">https://calypso.live?image=registry.a8c.com/calypso/app:build-142880&env=jetpack</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Automattic for Agencies live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-142880&env=a8c-for-agencies">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-142880%26env%3Da8c-for-agencies%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-142880&env=a8c-for-agencies">https://calypso.live?image=registry.a8c.com/calypso/app:build-142880&env=a8c-for-agencies</a>
</td>
</tr>
</table>
</details>
<!--calypso-live-watermark:apr@v1-->
author_association: NONE
user: login: matticbot
created_at: 2025-05-01T11:42:32Z
body: <!-- icfy-watermark: c52822 -->
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

**Sections** (~457 bytes added 📈 [gzipped])
<details>

```
name                      parsed_size           gzip_size
async-step-unified-plans      +1357 B  (+0.1%)     +379 B  (+0.1%)
plugins                       +1048 B  (+0.0%)     +253 B  (+0.0%)
plans                         +1048 B  (+0.1%)     +253 B  (+0.0%)
jetpack-app                   +1048 B  (+0.3%)     +253 B  (+0.2%)
overview                       +282 B  (+0.0%)      +49 B  (+0.0%)
checkout                        +75 B  (+0.0%)      +29 B  (+0.0%)
a8c-for-agencies-client         +75 B  (+0.0%)      +29 B  (+0.0%)
```

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.
</details>

**Async-loaded Components** (~550 bytes added 📈 [gzipped])
<details>

```
name                                                                              parsed_size           gzip_size
async-load-signup-steps-plans-theme-preselected                                       +1357 B  (+0.3%)     +385 B  (+0.3%)
async-load-signup-steps-plans                                                         +1357 B  (+0.3%)     +389 B  (+0.3%)
async-load-calypso-blocks-editor-checkout-modal                                         +75 B  (+0.0%)      +29 B  (+0.0%)
async-load-signup-steps-page-picker                                                     +72 B  (+0.0%)      +28 B  (+0.0%)
async-load-purchase-modal-wrapper                                                       +72 B  (+0.0%)      +28 B  (+0.0%)
async-load-my-sites-checkout-purchase-modal-is-eligible-for-one-click-checkou...        +72 B  (+0.0%)      +28 B  (+0.0%)
```

React components that are loaded lazily, when a certain part of UI is displayed for the first time.
</details>

**Legend**
<details>
<summary>What is parsed and gzip size?</summary>

**Parsed Size:** Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
**Gzip Size:** Compressed size of the JS and CSS files. This much data needs to be downloaded over network.
</details>

Generated by performance advisor bot at [iscalypsofastyet.com](http://iscalypsofastyet.com).
author_association: CONTRIBUTOR
user: login: matticbot
created_at: 2025-05-01T11:51:59Z
body: **This PR modifies the release build for the following Calypso Apps:**

_For info about this notification, see here: PCYsg-OT6-p2_

* help-center
* notifications

To test WordPress.com changes, run `install-plugin.sh $pluginSlug update/streamlined-pricing-experiment-plans` on your sandbox.
<!--calypso-app-artifacts-watermark:apr@v1-->
author_association: CONTRIBUTOR
user: login: a8ci18n
created_at: 2025-05-01T13:21:57Z
body: This Pull Request is now available for translation here: https://translate.wordpress.com/deliverables/17433576

Some locales (Hebrew, Japanese) have been temporarily machine-translated due to translator availability. All other translations are usually ready within a few days. Untranslated and machine-translated strings will be sent for translation next Monday and are expected to be completed by the following Friday.

Thank you @claudiucelfilip for including a screenshot in the description! This is really helpful for our translators.
author_association: NONE
user: login: claudiucelfilip
created_at: 2025-05-02T07:05:12Z
body: > The gist of it is there are plans that may be discounted through a promo offer (I think first-year currency discounts are also promo offers now). It creates ambiguity, but there was a bit to it that I don't recall precisely right now. The solution was the logic around `isAnyGridPlanDiscounted` / in #96631)

@chriskmnds  Thanks for catching that. It seems like a valid concern.
I double-checked with a first-year currency discount + a coupon and it seems the grid is behaving similar to production (ie. term savings are skipped) 

### Intro offer + coupon 
<img width="2928" alt="Screenshot 2025-05-02 at 09 57 51" src="https://github.com/user-attachments/assets/707398d2-c386-47b8-9eb6-d7c0de0bbb72" />
(left dev, right prod)

### Intro offer
<img width="2877" alt="Screenshot 2025-05-02 at 10 01 04" src="https://github.com/user-attachments/assets/794da99b-6c8b-4a96-8e2d-fdd5fe03085d" />
(left dev, right prod)

### Coupon
<img width="2885" alt="Screenshot 2025-05-02 at 10 02 42" src="https://github.com/user-attachments/assets/10ed0eda-1f47-49eb-88d5-ff1feaed6a63" />
(left dev, right prod)

### No offers, no coupon
<img width="2900" alt="Screenshot 2025-05-02 at 10 02 31" src="https://github.com/user-attachments/assets/f7452afe-81cc-4714-a964-de40fa529d3a" />
(left dev, right prod)

I'll bring this is up in the parent issue's discussions. If needed, we can follow up with changes to this behaviour.
author_association: CONTRIBUTOR
user: login: a8ci18n
created_at: 2025-05-04T21:28:45Z
body: Translation for this Pull Request [has now been finished](https://translate.wordpress.com/deliverables/overview/17433576).
author_association: NONE