From 0b15855de328e113470797c6520155b79fbbd6c0 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 11 Jul 2026 01:16:00 +0100 Subject: [PATCH] Align in-app Plans & Billing copy with the new public pricing vocabulary The public pricing surface (pulserelay.pro and the license-server pricing model, pulse-pro 3d9e365/2da1290) now sells Pro with the real Patrol mode names and plain-English job framing. The in-app plan cards still used the old taxonomy (Choose Patrol mode, Patrol investigates issues, Patrol handles safe fixes) and 'admin controls' phrasing. Pro plan-selling copy now matches the public wording: mode names spelled out (Ask first, Safe auto-fix, Autopilot), investigation explains the root cause, safe fixes are verified, and RBAC/audit/reporting/agent profiles are 'team controls'. Functional CTAs (the Choose Patrol mode link) and canonical mode labels are unchanged. Test pins updated. The Go-side feature catalog names (Patrol Investigates Issues / Patrol Handles Safe Fixes) still carry the old phrasing and are a separate follow-up since they regenerate from pkg/licensing. --- .../__tests__/ProLicensePanel.test.tsx | 23 +++++++---- .../__tests__/commercialBillingModel.test.ts | 2 +- .../__tests__/licensePresentation.test.ts | 40 +++++++++---------- .../utils/__tests__/selfHostedPlans.test.ts | 10 ++--- .../src/utils/licensePresentation.ts | 6 +-- frontend-modern/src/utils/selfHostedPlans.ts | 15 ++++--- 6 files changed, 50 insertions(+), 46 deletions(-) diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx index 3a47a02ed3..eb15810ab1 100644 --- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx @@ -310,12 +310,19 @@ describe('ProLicensePanel', () => { expect(screen.getByText('Remote web access via Relay')).toBeInTheDocument(); expect(screen.getByText('Pulse Mobile pairing')).toBeInTheDocument(); expect(screen.getByText('Push notifications')).toBeInTheDocument(); - expect(screen.getByText('Choose Patrol mode')).toBeInTheDocument(); - expect(screen.getByText('Patrol investigates issues')).toBeInTheDocument(); - expect(screen.getByText('Patrol handles safe fixes')).toBeInTheDocument(); + expect( + screen.getByText('Patrol modes: Ask first, Safe auto-fix, or Autopilot'), + ).toBeInTheDocument(); + expect( + screen.getByText('Patrol investigates issues and explains the root cause'), + ).toBeInTheDocument(); + expect( + screen.getByText('Patrol applies safe fixes and verifies the result'), + ).toBeInTheDocument(); expect(screen.getByText('90-day metric history')).toBeInTheDocument(); - expect(screen.getByText('RBAC and audit logging')).toBeInTheDocument(); - expect(screen.getByText('Agent profiles · PDF/CSV reports')).toBeInTheDocument(); + expect( + screen.getByText('Team controls: RBAC, audit logging, reporting, and agent profiles'), + ).toBeInTheDocument(); expect(screen.queryByText(/unlimited/i)).not.toBeInTheDocument(); expect(screen.queryByText(/trial/i)).not.toBeInTheDocument(); @@ -825,7 +832,7 @@ describe('ProLicensePanel', () => { expect(screen.getByText('Current plan: Pulse Pro')).toBeInTheDocument(); expect( screen.getByText( - 'Pulse Pro is active on this instance. It includes Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + 'Pulse Pro is active on this instance. It includes Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.', ), ).toBeInTheDocument(); expect(screen.getByText('Primary capabilities')).toBeInTheDocument(); @@ -851,7 +858,7 @@ describe('ProLicensePanel', () => { ), ).toBeInTheDocument(); expect(screen.getByText('Patrol investigation and remediation')).toBeInTheDocument(); - expect(screen.getByText('Team and admin controls')).toBeInTheDocument(); + expect(screen.getByText('Team controls')).toBeInTheDocument(); expect(screen.getAllByText('Active').length).toBeGreaterThanOrEqual(4); expect( screen.queryByText(new RegExp(['optional', 'activation'].join(' '), 'i')), @@ -863,7 +870,7 @@ describe('ProLicensePanel', () => { expect(screen.queryByText('Needs attention')).not.toBeInTheDocument(); expect(screen.queryByText('Available plans')).not.toBeInTheDocument(); expect(screen.getByText('90 days')).toBeInTheDocument(); - expect(screen.getByText('Patrol modes, history, and admin controls')).toBeInTheDocument(); + expect(screen.getByText('Patrol modes, history, and team controls')).toBeInTheDocument(); expect(screen.queryByText('Guest Capacity')).not.toBeInTheDocument(); expect(screen.queryByText('Included Monitored Systems')).not.toBeInTheDocument(); expect(screen.queryByText('Monitored-system policy')).not.toBeInTheDocument(); diff --git a/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts b/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts index eb1bb32ff8..b148dd990b 100644 --- a/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts +++ b/frontend-modern/src/utils/__tests__/commercialBillingModel.test.ts @@ -22,7 +22,7 @@ describe('commercialBillingModel', () => { expect(model.summary).toEqual([ { label: 'Core Monitoring', value: 'Included' }, { label: 'Metric History', value: '90 days' }, - { label: 'Included Extras', value: 'Patrol modes, history, and admin controls' }, + { label: 'Included Extras', value: 'Patrol modes, history, and team controls' }, ]); expect(model.details.map((item) => item.label)).toEqual([ 'Tier', diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index 0bd482cf3f..8608dae200 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -339,7 +339,7 @@ describe('licensePresentation', () => { }), ).toMatchObject({ title: 'Current plan: Pulse Pro', - body: 'Pulse Pro is active on this instance. It includes Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + body: 'Pulse Pro is active on this instance. It includes Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.', supplementalBadges: [], supplementalSummary: '', }); @@ -362,7 +362,7 @@ describe('licensePresentation', () => { }), ).toEqual({ title: 'Current plan: Pulse Pro', - body: 'Pulse Pro is active on this instance. It includes Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + body: 'Pulse Pro is active on this instance. It includes Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.', unlockedFeaturesLabel: 'Primary capabilities', unlockedFeatures: [ 'Patrol Investigates Issues', @@ -633,7 +633,7 @@ describe('licensePresentation', () => { }), ).toMatchObject({ title: 'Current plan: Legacy Pulse Pro+', - body: 'Legacy Pulse Pro+ is active on this instance. It includes Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + body: 'Legacy Pulse Pro+ is active on this instance. It includes Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.', unlockedFeaturesLabel: 'Primary capabilities', unlockedFeatures: [ 'Patrol Investigates Issues', @@ -657,7 +657,7 @@ describe('licensePresentation', () => { }), ).toEqual({ title: 'Current plan: Pulse Pro', - body: 'Pulse Pro is active on this instance. It includes Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + body: 'Pulse Pro is active on this instance. It includes Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.', unlockedFeaturesLabel: 'Primary capabilities', unlockedFeatures: [ 'Patrol Investigates Issues', @@ -903,15 +903,14 @@ describe('licensePresentation', () => { }, { title: 'Pulse Pro plan', - body: 'Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + body: 'Patrol investigates issues, applies safe fixes, and verifies the result, plus 90-day metric history and team controls.', highlights: [ 'Everything in Relay', - 'Choose Patrol mode', - 'Patrol investigates issues', - 'Patrol handles safe fixes', + 'Patrol modes: Ask first, Safe auto-fix, or Autopilot', + 'Patrol investigates issues and explains the root cause', + 'Patrol applies safe fixes and verifies the result', '90-day metric history', - 'RBAC and audit logging', - 'Agent profiles · PDF/CSV reports', + 'Team controls: RBAC, audit logging, reporting, and agent profiles', ], }, ], @@ -931,15 +930,14 @@ describe('licensePresentation', () => { cards: [ { title: 'Pulse Pro plan', - body: 'Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + body: 'Patrol investigates issues, applies safe fixes, and verifies the result, plus 90-day metric history and team controls.', highlights: [ 'Everything in Relay', - 'Choose Patrol mode', - 'Patrol investigates issues', - 'Patrol handles safe fixes', + 'Patrol modes: Ask first, Safe auto-fix, or Autopilot', + 'Patrol investigates issues and explains the root cause', + 'Patrol applies safe fixes and verifies the result', '90-day metric history', - 'RBAC and audit logging', - 'Agent profiles · PDF/CSV reports', + 'Team controls: RBAC, audit logging, reporting, and agent profiles', ], }, ], @@ -1018,7 +1016,7 @@ describe('licensePresentation', () => { { label: 'Remote access, pairing, and push', statusLabel: 'Active' }, { label: '90-day metric history', statusLabel: 'Active' }, { label: 'Patrol investigation and remediation', statusLabel: 'Active' }, - { label: 'Team and admin controls', statusLabel: 'Active' }, + { label: 'Team controls', statusLabel: 'Active' }, ], }); @@ -1036,7 +1034,7 @@ describe('licensePresentation', () => { ['Remote access, pairing, and push', 'Partial'], ['90-day metric history', 'Partial'], ['Patrol investigation and remediation', 'Partial'], - ['Team and admin controls', 'Needs attention'], + ['Team controls', 'Needs attention'], ]); expect(partialProStatus?.items).toContainEqual({ label: 'Patrol investigation and remediation', @@ -1046,11 +1044,11 @@ describe('licensePresentation', () => { 'Some Patrol capability is available, but investigation or remediation may not be fully enabled yet.', }); expect(partialProStatus?.items).toContainEqual({ - label: 'Team and admin controls', + label: 'Team controls', state: 'missing', statusLabel: 'Needs attention', detail: - 'Team/admin controls are not available yet. Refresh the plan or open recovery before relying on this Pro install.', + 'Team controls are not available yet. Refresh the plan or open recovery before relying on this Pro install.', }); expect(partialProStatus?.items.map((item) => item.detail).join(' ')).not.toContain( 'admin tools', @@ -1093,7 +1091,7 @@ describe('licensePresentation', () => { ['Remote access, pairing, and push', 'Active'], ['90-day metric history', 'Active'], ['Patrol investigation and remediation', 'Active'], - ['Team and admin controls', 'Active'], + ['Team controls', 'Active'], ]; const expectOnlyCapabilityItems = ( status?: ReturnType, diff --git a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts index f901ac876f..07f84d854d 100644 --- a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts +++ b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts @@ -126,13 +126,13 @@ describe('selfHostedPlans', () => { /yourlab\.pulserelay\.pro|custom\s+(?:url|subdomain|domain)/i, ); expect(SELF_HOSTED_PLAN_BY_TIER.pro.billingExtrasSummary).toBe( - 'Patrol modes, history, and admin controls', + 'Patrol modes, history, and team controls', ); expect(SELF_HOSTED_PLAN_BY_TIER.pro.entitlementSummary).toContain( - 'Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + 'Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.', ); expect(SELF_HOSTED_PLAN_BY_TIER.pro.comparisonSummary).toBe( - 'Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + 'Patrol investigates issues, applies safe fixes, and verifies the result, plus 90-day metric history and team controls.', ); expect(SELF_HOSTED_PLAN_BY_TIER.pro.comparisonSummary).not.toContain('how much control'); expect(SELF_HOSTED_PLAN_BY_TIER.pro.entitlementHighlights).toEqual([ @@ -172,7 +172,7 @@ describe('selfHostedPlans', () => { getSelfHostedPlanEntitlementSummary('pro'), ); expect(getSelfHostedPlanEntitlementSummary('pro', 'Legacy Pulse Pro+')).toBe( - 'Legacy Pulse Pro+ is active on this instance. It includes Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + 'Legacy Pulse Pro+ is active on this instance. It includes Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.', ); expect(SELF_HOSTED_PLAN_BY_TIER.relay.entitlementHighlights).toEqual( @@ -216,7 +216,7 @@ describe('selfHostedPlans', () => { historyDays: 14, extras: 'Remote web access, pairing, and push', }, - { tier: 'pro', historyDays: 90, extras: 'Patrol modes, history, and admin controls' }, + { tier: 'pro', historyDays: 90, extras: 'Patrol modes, history, and team controls' }, ]); expect(SELF_HOSTED_FEATURE_ROWS.find((row) => row.key === 'history')).toEqual({ key: 'history', diff --git a/frontend-modern/src/utils/licensePresentation.ts b/frontend-modern/src/utils/licensePresentation.ts index 04400ed066..cdf715b97d 100644 --- a/frontend-modern/src/utils/licensePresentation.ts +++ b/frontend-modern/src/utils/licensePresentation.ts @@ -924,14 +924,14 @@ export const getSelfHostedPlanStatusPresentation = ( }), buildCapabilityStatusItem({ capabilities, - label: 'Team and admin controls', + label: 'Team controls', requiredCapabilities: ['rbac', 'audit_logging', 'advanced_reporting', 'agent_profiles'], activeDetail: 'RBAC, audit logging, reporting, and agent profiles are available for governed operations.', partialDetail: - 'Some team/admin capabilities are available. Refresh the plan or open recovery if any admin controls stay unavailable.', + 'Some team controls are available. Refresh the plan or open recovery if any of them stay unavailable.', missingDetail: - 'Team/admin controls are not available yet. Refresh the plan or open recovery before relying on this Pro install.', + 'Team controls are not available yet. Refresh the plan or open recovery before relying on this Pro install.', }), ); } diff --git a/frontend-modern/src/utils/selfHostedPlans.ts b/frontend-modern/src/utils/selfHostedPlans.ts index ab7870d311..5a50a83f3d 100644 --- a/frontend-modern/src/utils/selfHostedPlans.ts +++ b/frontend-modern/src/utils/selfHostedPlans.ts @@ -84,7 +84,7 @@ export function getSelfHostedPlanEntitlementSummary( case 'relay': return `${planLabel} is active on this instance. It includes remote web access, Pulse Mobile pairing, push notifications, and 14-day metric history.`; case 'pro': - return `${planLabel} is active on this instance. It includes Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.`; + return `${planLabel} is active on this instance. It includes Patrol modes (Ask first, Safe auto-fix, Autopilot), 90-day metric history, RBAC, audit logging, reporting, and agent profiles.`; } } @@ -150,20 +150,19 @@ export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] = price: '$79/year', subline: 'or $8.99/month', metricHistoryDays: 90, - billingExtrasSummary: 'Patrol modes, history, and admin controls', + billingExtrasSummary: 'Patrol modes, history, and team controls', entitlementSummary: getSelfHostedPlanEntitlementSummary('pro'), entitlementHighlights: getTierEntitlementHighlights('pro', 90), includedExtras: getTierIncludedExtras('pro'), comparisonSummary: - 'Patrol modes, 90-day metric history, RBAC, audit logs, reports, and agent profiles.', + 'Patrol investigates issues, applies safe fixes, and verifies the result, plus 90-day metric history and team controls.', highlights: [ 'Everything in Relay', - 'Choose Patrol mode', - 'Patrol investigates issues', - 'Patrol handles safe fixes', + 'Patrol modes: Ask first, Safe auto-fix, or Autopilot', + 'Patrol investigates issues and explains the root cause', + 'Patrol applies safe fixes and verifies the result', '90-day metric history', - 'RBAC and audit logging', - 'Agent profiles · PDF/CSV reports', + 'Team controls: RBAC, audit logging, reporting, and agent profiles', ], }, ] as const;