From aa6dc760926cde09a2ea156de424bfa072758ae8 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 26 Mar 2026 23:03:57 +0000 Subject: [PATCH] Move cloud commercial copy into shared contract --- .../v6/internal/subsystems/cloud-paid.md | 4 +++ frontend-modern/src/pages/CloudPricing.tsx | 28 +++++++----------- .../src/pages/__tests__/CloudPricing.test.tsx | 4 +++ .../src/utils/__tests__/cloudPlans.test.ts | 23 +++++++++++++++ frontend-modern/src/utils/cloudPlans.ts | 29 +++++++++++++++++++ 5 files changed, 71 insertions(+), 17 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 1f8d96a54..a86190f03 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -508,6 +508,10 @@ headline price, founding-rate override, compare-at strike-through copy, campaign badge copy, and annual summary text must come from the shared plan-definition owners rather than page-local string parsing or hardcoded retail amounts inside hosted pricing/signup screens. +The same owner also holds shared hosted commercial copy such as page title, +introductory description, common Cloud inclusions, and setup-step guidance +when those facts describe the canonical offer rather than one page's local +layout. That same counted-unit boundary also owns the disclosure rule for retail copy: default billing and pricing surfaces should use concise monitored-system copy, while the full counted-unit definition appears only behind explicit disclosure diff --git a/frontend-modern/src/pages/CloudPricing.tsx b/frontend-modern/src/pages/CloudPricing.tsx index d6b8cee66..74eb02a99 100644 --- a/frontend-modern/src/pages/CloudPricing.tsx +++ b/frontend-modern/src/pages/CloudPricing.tsx @@ -5,20 +5,12 @@ import { PageHeader } from '@/components/shared/PageHeader'; import { trackPaywallViewed } from '@/utils/upgradeMetrics'; import { onMount } from 'solid-js'; import { + CLOUD_COMMERCIAL_PRESENTATION, CLOUD_PLAN_DEFINITIONS, getCloudPlanPricePresentation, type CloudPlanDefinition, } from '@/utils/cloudPlans'; -const INCLUDED_IN_ALL = [ - 'All Pro features', - 'Managed hosting', - 'Daily backups', - 'Secure agent connectivity via Relay', - 'Mobile app access and push notifications', - 'Dedicated workspace URL', -]; - function CloudTierCard(props: { tier: CloudPlanDefinition }) { const t = props.tier; const price = getCloudPlanPricePresentation(t); @@ -95,8 +87,8 @@ export default function CloudPricing() { return (
{/* Tier cards */} @@ -106,9 +98,11 @@ export default function CloudPricing() { {/* What's included in all Cloud plans */} -

Included in every Cloud plan

+

+ {CLOUD_COMMERCIAL_PRESENTATION.includedInAllHeading} +

    - + {(item) => (
  • @@ -121,11 +115,11 @@ export default function CloudPricing() { {/* How it works */} -

    Setup

    +

    + {CLOUD_COMMERCIAL_PRESENTATION.setupHeading} +

      -
    1. Create your workspace. No credit card is required for the trial.
    2. -
    3. Install the Pulse agent on any Linux machine.
    4. -
    5. Connect systems, review findings, and configure alerts.
    6. + {(step) =>
    7. {step}
    8. }
    diff --git a/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx b/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx index 6213bd30e..57a599cf4 100644 --- a/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx +++ b/frontend-modern/src/pages/__tests__/CloudPricing.test.tsx @@ -37,6 +37,10 @@ describe('CloudPricing', () => { ); expect(screen.queryByText('Starter founding rate')).not.toBeInTheDocument(); expect(screen.getAllByText('All Pro features')).toHaveLength(1); + expect(screen.getByText('Managed hosting')).toBeInTheDocument(); + expect( + screen.getByText('Create your workspace. No credit card is required for the trial.'), + ).toBeInTheDocument(); expect(screen.getByText('Setup')).toBeInTheDocument(); expect( screen.queryByText(/provisioned in under 60 seconds/i), diff --git a/frontend-modern/src/utils/__tests__/cloudPlans.test.ts b/frontend-modern/src/utils/__tests__/cloudPlans.test.ts index 840ba0834..a4b4aed99 100644 --- a/frontend-modern/src/utils/__tests__/cloudPlans.test.ts +++ b/frontend-modern/src/utils/__tests__/cloudPlans.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest'; import { + CLOUD_COMMERCIAL_PRESENTATION, CLOUD_PLAN_BY_TIER, getCloudPlanPricePresentation, } from '@/utils/cloudPlans'; @@ -25,4 +26,26 @@ describe('cloudPlans', () => { campaignBadge: undefined, }); }); + + it('keeps shared cloud commercial copy in the common contract', () => { + expect(CLOUD_COMMERCIAL_PRESENTATION).toEqual({ + pageTitle: 'Pulse Cloud', + pageDescription: 'Managed Pulse hosting with Pro features included.', + includedInAllHeading: 'Included in every Cloud plan', + includedInAllItems: [ + 'All Pro features', + 'Managed hosting', + 'Daily backups', + 'Secure agent connectivity via Relay', + 'Mobile app access and push notifications', + 'Dedicated workspace URL', + ], + setupHeading: 'Setup', + setupSteps: [ + 'Create your workspace. No credit card is required for the trial.', + 'Install the Pulse agent on any Linux machine.', + 'Connect systems, review findings, and configure alerts.', + ], + }); + }); }); diff --git a/frontend-modern/src/utils/cloudPlans.ts b/frontend-modern/src/utils/cloudPlans.ts index e9a8dd22a..ac10fb4f3 100644 --- a/frontend-modern/src/utils/cloudPlans.ts +++ b/frontend-modern/src/utils/cloudPlans.ts @@ -21,6 +21,15 @@ export interface CloudPlanPricePresentation { campaignBadge?: string; } +export interface CloudCommercialPresentation { + pageTitle: string; + pageDescription: string; + includedInAllHeading: string; + includedInAllItems: readonly string[]; + setupHeading: string; + setupSteps: readonly string[]; +} + export const DEFAULT_CLOUD_TIER: CloudTierKey = 'starter'; export const CLOUD_PLAN_DEFINITIONS: readonly CloudPlanDefinition[] = [ @@ -72,6 +81,26 @@ export const CLOUD_PLAN_LABELS: Record = { msp_scale: 'MSP Scale', }; +export const CLOUD_COMMERCIAL_PRESENTATION: CloudCommercialPresentation = { + pageTitle: 'Pulse Cloud', + pageDescription: 'Managed Pulse hosting with Pro features included.', + includedInAllHeading: 'Included in every Cloud plan', + includedInAllItems: [ + 'All Pro features', + 'Managed hosting', + 'Daily backups', + 'Secure agent connectivity via Relay', + 'Mobile app access and push notifications', + 'Dedicated workspace URL', + ], + setupHeading: 'Setup', + setupSteps: [ + 'Create your workspace. No credit card is required for the trial.', + 'Install the Pulse agent on any Linux machine.', + 'Connect systems, review findings, and configure alerts.', + ], +} as const; + export function parseCloudTier(value?: string | null): CloudTierKey { switch ((value || '').trim().toLowerCase()) { case 'power':