diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 241bc169b0..9c4cd7473f 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -481,6 +481,10 @@ top-level Pulse Pro surface must keep its page-shell title and leading SettingsPanel title aligned so commercial activation, trial, and pricing state do not present as one surface in navigation and a differently named surface in the actual paid settings UI. +That same boundary also owns the shell and section framing copy for the +self-hosted Pro billing surface. `ProLicensePanel.tsx` must consume shared +presentation for its shell title/description, refresh CTA label, and section +headings rather than carrying those commercial labels inline. Paid Pulse Pro v5 grandfathering is now part of that same canonical boundary: when a recurring v5 customer migrates into v6, billing persistence, entitlement evaluation, renewal handling, and Pro-license presentation must diff --git a/frontend-modern/src/components/Settings/ProLicensePanel.tsx b/frontend-modern/src/components/Settings/ProLicensePanel.tsx index 9b0f03e575..6d39528d8f 100644 --- a/frontend-modern/src/components/Settings/ProLicensePanel.tsx +++ b/frontend-modern/src/components/Settings/ProLicensePanel.tsx @@ -6,6 +6,7 @@ import { CommercialBillingShell, CommercialSection } from './CommercialBillingSe import { ProLicensePlanSection } from './ProLicensePlanSection'; import { SelfHostedCommercialActivationSection } from './SelfHostedCommercialActivationSection'; import { useProLicensePanelState } from './useProLicensePanelState'; +import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from '@/utils/licensePresentation'; import { getMonitoredSystemBriefSummary } from '@/utils/monitoredSystemPresentation'; export const ProLicensePanel: Component = () => { @@ -14,8 +15,8 @@ export const ProLicensePanel: Component = () => { return (
} action={ } loading={false} >
{ diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx index 6b1443bab5..31d7fc0e7b 100644 --- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx @@ -458,6 +458,7 @@ describe('ProLicensePanel', () => { expect(proLicensePanelSource).toContain('./useProLicensePanelState'); expect(proLicensePanelSource).toContain('./ProLicensePlanSection'); expect(proLicensePanelSource).toContain('SelfHostedCommercialActivationSection'); + expect(proLicensePanelSource).toContain('SELF_HOSTED_PRO_BILLING_PRESENTATION'); expect(proLicensePanelSource).not.toContain('createSignal('); expect(proLicensePanelSource).not.toContain('useLocation()'); expect(proLicensePanelStateSource).toContain('useLocation'); diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index fc7c0b0ab7..c46c4b0c48 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -533,10 +533,16 @@ describe('Settings architecture guardrails', () => { ); expect(monitoredSystemDefinitionDisclosureSource).not.toContain('summary?: string'); expect(monitoredSystemDefinitionDisclosureSource).not.toContain('{props.summary}'); + expect(proLicensePanelSource).toContain('@/utils/licensePresentation'); + expect(proLicensePanelSource).toContain('SELF_HOSTED_PRO_BILLING_PRESENTATION'); expect(proLicensePanelStateSource).toContain('buildSelfHostedCommercialPlanModel'); expect(proLicensePanelStateSource).toContain('loadLicenseStatus(true)'); expect(proLicensePanelStateSource).toContain('runStartProTrialAction({'); expect(proLicensePanelStateSource).not.toContain('startProTrial()'); + expect(proLicensePanelSource).not.toContain('title="Pulse Pro"'); + expect(proLicensePanelSource).not.toContain('description="Manage self-hosted billing'); + expect(proLicensePanelSource).not.toContain('title="Plan"'); + expect(proLicensePanelSource).not.toContain('title="Usage"'); expect(proLicensePlanSectionSource).toContain('CommercialStatGrid'); expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState'); expect(monitoredSystemPresentationSource).toContain( @@ -1279,7 +1285,11 @@ describe('Settings architecture guardrails', () => { title, ); const allowedTitleExpression = - tab === 'system-updates' ? `title={UPDATES_PANEL_COPY.title}` : `title="${title}"`; + tab === 'system-updates' + ? `title={UPDATES_PANEL_COPY.title}` + : tab === 'system-billing' + ? 'title={SELF_HOSTED_PRO_BILLING_PRESENTATION.shellTitle}' + : `title="${title}"`; expect( source, `${tab} should keep its leading SettingsPanel title aligned with the shell title`, diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index 03bbee0609..263a7c5d6e 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -19,6 +19,7 @@ import { getTrialActivationNotice, isDisplayableLicenseFeature, SELF_HOSTED_ACTIVATION_PRESENTATION, + SELF_HOSTED_PRO_BILLING_PRESENTATION, } from '@/utils/licensePresentation'; describe('licensePresentation', () => { @@ -61,6 +62,16 @@ describe('licensePresentation', () => { idleActionLabel: 'Start 14-day Pro Trial', }, }); + expect(SELF_HOSTED_PRO_BILLING_PRESENTATION).toEqual({ + shellTitle: 'Pulse Pro', + shellDescription: + 'Manage self-hosted billing, monitored-system limits, and Pulse Pro license status.', + refreshLabel: 'Refresh', + planSectionTitle: 'Plan', + planSectionDescription: + 'Review your active plan, expiry, included limits, and paid capabilities.', + usageSectionTitle: 'Usage', + }); }); it('returns canonical feature labels', () => { diff --git a/frontend-modern/src/utils/licensePresentation.ts b/frontend-modern/src/utils/licensePresentation.ts index a11495aa44..7bcba57cc0 100644 --- a/frontend-modern/src/utils/licensePresentation.ts +++ b/frontend-modern/src/utils/licensePresentation.ts @@ -102,6 +102,15 @@ export interface SelfHostedActivationPresentation { trial: SelfHostedActivationTrialCopy; } +export interface SelfHostedProBillingPresentation { + shellTitle: string; + shellDescription: string; + refreshLabel: string; + planSectionTitle: string; + planSectionDescription: string; + usageSectionTitle: string; +} + const GRANDFATHERED_V5_PLAN_LABELS: Record = { v5_lifetime_grandfathered: 'V5 Lifetime Grandfathered', v5_pro_monthly_grandfathered: 'V5 Pro Monthly (Grandfathered)', @@ -362,6 +371,17 @@ export const SELF_HOSTED_ACTIVATION_PRESENTATION: SelfHostedActivationPresentati }, }; +export const SELF_HOSTED_PRO_BILLING_PRESENTATION: SelfHostedProBillingPresentation = { + shellTitle: 'Pulse Pro', + shellDescription: + 'Manage self-hosted billing, monitored-system limits, and Pulse Pro license status.', + refreshLabel: 'Refresh', + planSectionTitle: 'Plan', + planSectionDescription: + 'Review your active plan, expiry, included limits, and paid capabilities.', + usageSectionTitle: 'Usage', +}; + export const getOrganizationBillingLicenseStatusLabel = ( status?: Pick | null, ): string => {