diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index a4e01d69c..4f7d90594 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -1394,7 +1394,11 @@ when a migrated recurring v5 plan is active or in grace, the settings surface must render plan terms and a continuity notice that makes it clear the existing recurring price remains in force until cancellation, while self-hosted monitoring and child-resource volume stay uncapped under the -current v6 policy. +current v6 policy. The same plan summary must not render a separate +`Guest Capacity`, child-resource allowance, or equivalent volume-cap row for +uncapped/grandfathered self-hosted plans; the customer-facing continuity story +is existing-price protection plus uncapped self-hosted monitoring and +child-resource volume, not a new paid guest-capacity benefit. The self-hosted commercial presentation on that same surface is now locked to the no-cap monitored-system model as well. `ProLicensePanel.tsx`, `CommercialBillingSections.tsx`, and diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx index 396b2e4ae..f1d17b29b 100644 --- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx @@ -375,7 +375,7 @@ describe('ProLicensePanel', () => { ), ).toBeInTheDocument(); expect(screen.queryByText('Included Monitored Systems')).not.toBeInTheDocument(); - expect(screen.getByText('Guest Capacity')).toBeInTheDocument(); + expect(screen.queryByText('Guest Capacity')).not.toBeInTheDocument(); expect(screen.getByText('Core Monitoring')).toBeInTheDocument(); expect(screen.queryByText('Monitored Systems')).not.toBeInTheDocument(); expect(screen.queryByText('Capacity Status')).not.toBeInTheDocument(); @@ -423,17 +423,17 @@ describe('ProLicensePanel', () => { expect(screen.getByText('Grandfathered price')).toBeInTheDocument(); expect( screen.getAllByText( - /keeps its existing recurring price and uncapped monitored-system and guest capacity/i, + /keeps its existing recurring price until/i, ).length, ).toBeGreaterThan(0); expect( screen.queryByText(/keeps its existing recurring price and uncapped guest capacity/i), ).not.toBeInTheDocument(); expect( - screen.getByText( - /keeps its existing recurring price and uncapped monitored-system and guest capacity until you cancel/i, - ), - ).toBeInTheDocument(); + screen.getAllByText( + /self-hosted monitoring and child-resource volume remain uncapped under the current v6 policy/i, + ).length, + ).toBeGreaterThan(0); expect( within(screen.getByText('Core Monitoring').parentElement as HTMLElement).getByText( 'Unlimited', @@ -441,7 +441,7 @@ describe('ProLicensePanel', () => { ).toBeInTheDocument(); expect(screen.queryByText('Capacity Status')).not.toBeInTheDocument(); expect(screen.queryByText('Included Monitored Systems')).not.toBeInTheDocument(); - expect(screen.getByText('Guest Capacity')).toBeInTheDocument(); + expect(screen.queryByText('Guest Capacity')).not.toBeInTheDocument(); expect(screen.queryByText('Monitored-system policy')).not.toBeInTheDocument(); expect(screen.queryByRole('tab', { name: 'Usage' })).not.toBeInTheDocument(); expect(screen.getAllByText('Unlimited').length).toBeGreaterThan(0); diff --git a/frontend-modern/src/components/Settings/useProLicensePanelState.ts b/frontend-modern/src/components/Settings/useProLicensePanelState.ts index 514cd4d80..82c5526fa 100644 --- a/frontend-modern/src/components/Settings/useProLicensePanelState.ts +++ b/frontend-modern/src/components/Settings/useProLicensePanelState.ts @@ -162,9 +162,6 @@ export function useProLicensePanelState() { const monitoredSystemCapacity = createMemo(() => uncappedGrandfatheredPlan() ? undefined : entitlements()?.monitored_system_capacity, ); - const guestLimitStatus = createMemo(() => - uncappedGrandfatheredPlan() ? undefined : limitStatus('max_guests'), - ); const displayableMonitoredSystemContinuity = createMemo(() => getDisplayableMonitoredSystemContinuity({ continuity: monitoredSystemContinuity(), @@ -507,17 +504,6 @@ export function useProLicensePanelState() { ), ); - const showGuestCapacity = createMemo(() => { - if (currentRetailPlanDefinition()) { - return false; - } - if (uncappedGrandfatheredPlan()) { - return true; - } - const guestLimit = guestLimitStatus()?.limit; - return typeof guestLimit === 'number' && guestLimit > 0; - }); - const commercialPlanModel = createMemo(() => buildSelfHostedCommercialPlanModel({ licensedEmail: entitlements()?.licensed_email, @@ -533,12 +519,7 @@ export function useProLicensePanelState() { monitoredSystemLimitStatus()!.limit > 0 ? monitoredSystemLimitStatus()!.limit : 'Unlimited', - guestCapacity: - typeof guestLimitStatus()?.limit === 'number' && guestLimitStatus()!.limit > 0 - ? guestLimitStatus()!.limit - : 'Unlimited', retailPlanDefinition: currentRetailPlanDefinition(), - showGuestCapacity: showGuestCapacity(), monitoredSystemContinuity: displayableMonitoredSystemContinuity() ?? null, continuityCapturedAt: continuityCapturedAt(), }), diff --git a/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts b/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts index 77357c9fc..433f3298d 100644 --- a/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts +++ b/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts @@ -13,7 +13,6 @@ const createBaseInput = () => ({ monitoredSystemsSummary: 'Unlimited', capacityStatusSummary: 'Unlimited', maxMonitoredSystems: 'Unlimited' as const, - guestCapacity: 'Unlimited' as const, }); describe('commercialBillingModel', () => { @@ -21,7 +20,6 @@ describe('commercialBillingModel', () => { const model = buildSelfHostedCommercialPlanModel({ ...createBaseInput(), retailPlanDefinition: SELF_HOSTED_PLAN_BY_TIER.pro, - showGuestCapacity: false, }); expect(model.summary).toEqual([ @@ -38,20 +36,19 @@ describe('commercialBillingModel', () => { ]); }); - it('keeps guest capacity visible for uncapped grandfathered continuity states', () => { + it('keeps uncapped grandfathered continuity focused on core monitoring', () => { const model = buildSelfHostedCommercialPlanModel({ ...createBaseInput(), planTerms: 'V5 Pro Monthly (Grandfathered)', retailPlanDefinition: null, - showGuestCapacity: true, }); expect(model.summary).toEqual([ { label: 'Core Monitoring', value: 'Unlimited' }, - { label: 'Guest Capacity', value: 'Unlimited' }, { label: 'Plan Status', value: 'Active' }, ]); expect(model.details.map((item) => item.label)).not.toContain('Included Monitored Systems'); + expect(model.summary.map((item) => item.label)).not.toContain('Guest Capacity'); }); it('keeps bounded monitored-system details on legacy fallback paths', () => { @@ -60,9 +57,7 @@ describe('commercialBillingModel', () => { monitoredSystemsSummary: '7 monitored systems', capacityStatusSummary: '3 remaining', maxMonitoredSystems: 10, - guestCapacity: 50, retailPlanDefinition: null, - showGuestCapacity: true, }); expect(model.summary).toEqual([ diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index e542006b4..706e0c643 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -193,8 +193,14 @@ describe('licensePresentation', () => { ).toMatchObject({ title: 'Grandfathered v5 pricing', tone: expect.stringContaining('green'), - body: expect.stringContaining('uncapped monitored-system and guest capacity'), + body: expect.stringContaining('keeps its existing recurring price until you cancel'), }); + expect( + getGrandfatheredPriceContinuityNotice('v5_pro_monthly_grandfathered', 'active')?.body, + ).toContain('Self-hosted monitoring and child-resource volume remain uncapped'); + expect( + getGrandfatheredPriceContinuityNotice('v5_pro_monthly_grandfathered', 'active')?.body, + ).not.toContain('guest capacity'); expect( getGrandfatheredPriceContinuityNotice('v5_pro_annual_grandfathered', 'grace'), ).toMatchObject({ @@ -343,7 +349,7 @@ describe('licensePresentation', () => { ], supplementalBadges: ['Grandfathered price'], supplementalSummary: - 'This migrated v5 subscription keeps its existing recurring price and uncapped monitored-system and guest capacity until cancellation.', + 'This migrated v5 subscription keeps its existing recurring price until cancellation. Self-hosted monitoring and child-resource volume remain uncapped under the current v6 policy.', }); }); diff --git a/frontend-modern/src/utils/commercialBillingModel.ts b/frontend-modern/src/utils/commercialBillingModel.ts index 74d915262..4433a4d17 100644 --- a/frontend-modern/src/utils/commercialBillingModel.ts +++ b/frontend-modern/src/utils/commercialBillingModel.ts @@ -33,12 +33,10 @@ export interface SelfHostedCommercialModelInput { monitoredSystemsSummary: string | number; capacityStatusSummary: string | number; maxMonitoredSystems: string | number; - guestCapacity: string | number; retailPlanDefinition?: Pick< SelfHostedPlanDefinition, 'billingExtrasSummary' | 'metricHistoryDays' > | null; - showGuestCapacity?: boolean; monitoredSystemContinuity?: MonitoredSystemContinuityStatus | null; continuityCapturedAt?: string; } @@ -123,14 +121,6 @@ export const buildSelfHostedCommercialPlanModel = ( label: 'Core Monitoring', value: 'Unlimited', }, - ...(input.showGuestCapacity - ? [ - { - label: 'Guest Capacity', - value: input.guestCapacity, - }, - ] - : []), { label: 'Plan Status', value: input.statusLabel, diff --git a/frontend-modern/src/utils/licensePresentation.ts b/frontend-modern/src/utils/licensePresentation.ts index ea35f3b41..d80b33e91 100644 --- a/frontend-modern/src/utils/licensePresentation.ts +++ b/frontend-modern/src/utils/licensePresentation.ts @@ -251,7 +251,7 @@ export const getGrandfatheredPriceContinuityNotice = ( return { tone: 'border-green-200 dark:border-green-900 bg-green-50 dark:bg-green-900 text-green-900 dark:text-green-100', title: 'Grandfathered v5 pricing', - body: 'This migrated v5 Pro subscription keeps its existing recurring price and uncapped monitored-system and guest capacity until you cancel. If you cancel and return later, current v6 pricing applies; public self-hosted monitoring remains unlimited.', + body: 'This migrated v5 Pro subscription keeps its existing recurring price until you cancel. Self-hosted monitoring and child-resource volume remain uncapped under the current v6 policy. If you cancel and return later, current v6 pricing applies for paid features.', }; }; @@ -481,12 +481,12 @@ export const getSelfHostedCurrentPlanPresentation = ({ ) { supplementalBadges.push('Grandfathered price'); supplementalDetails.push( - 'This migrated v5 subscription keeps its existing recurring price and uncapped monitored-system and guest capacity until cancellation.', + 'This migrated v5 subscription keeps its existing recurring price until cancellation. Self-hosted monitoring and child-resource volume remain uncapped under the current v6 policy.', ); } else if (hasUncappedContinuity && current.is_lifetime) { supplementalBadges.push('Grandfathered lifetime'); supplementalDetails.push( - 'This migrated lifetime install keeps uncapped monitored-system and guest capacity continuity.', + 'This migrated lifetime install remains valid permanently, with uncapped self-hosted monitoring and child-resource volume.', ); }