From f060f261cd331990d7ebbf21e1b0958432082c7d Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 29 Apr 2026 12:49:20 +0100 Subject: [PATCH] Present Relay as annual-first support tier --- README.md | 4 +-- docs/FAQ.md | 2 +- docs/PRIVACY.md | 2 +- docs/PULSE_PRO.md | 4 +-- docs/README.md | 2 +- ...hosted-commercial-model-lock-2026-03-17.md | 25 ++++++++----------- .../v6/internal/subsystems/cloud-paid.md | 7 ++++++ docs/releases/RELEASE_NOTES_v6.md | 2 +- docs/releases/RELEASE_NOTES_v6_RC2_DRAFT.md | 2 +- docs/releases/V6_CHANGELOG_RC2_DRAFT.md | 2 +- .../V6_RC2_OPERATOR_SUPPORT_PACK_DRAFT.md | 2 +- frontend-modern/public/docs/PRIVACY.md | 2 +- frontend-modern/public/docs/README.md | 4 +-- .../__tests__/ProLicensePanel.test.tsx | 16 ++++++------ .../__tests__/licensePresentation.test.ts | 14 +++++------ .../utils/__tests__/selfHostedPlans.test.ts | 10 ++++---- .../src/utils/licensePresentation.ts | 2 +- .../selfHostedFeatureCatalog.generated.ts | 4 +-- frontend-modern/src/utils/selfHostedPlans.ts | 20 +++++++-------- .../cloudcp/portal/dist/build_manifest.json | 2 +- internal/cloudcp/portal/frontend/dev.mjs | 22 ++++++++-------- .../portal/frontend/src/billing_view.test.ts | 14 +++++------ internal/license/coverage_additional_test.go | 2 +- pkg/licensing/features_test.go | 2 +- pkg/licensing/self_hosted_feature_catalog.go | 6 ++--- ...56-pulse-account-upgrade-bootstrap.spec.ts | 10 ++++---- 26 files changed, 94 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 0ac2e9929..23093c350 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,8 @@ Self-hosted tiers: | Plan | Price | Core monitoring | Metric history | Main value | |---|---:|---|---:|---| | Community | Free | Included | 7 days | Full self-hosted monitoring | -| Relay | $4.99/mo or $39/yr | Included | 14 days | Remote access, mobile, and push notifications | -| Pro | $8.99/mo or $79/yr | Included | 90 days | Root-cause analysis, safe remediation workflows, and operations tooling | +| Relay | $39/yr or $4.99/mo | Included | 14 days | Remote web access, mobile app pairing, and push notifications | +| Pro | $79/yr or $8.99/mo | Included | 90 days | Root-cause analysis, safe remediation workflows, and operations tooling | Pulse still counts top-level monitored systems once no matter how they are collected. VMs, containers, pods, disks, backups, and other child resources diff --git a/docs/FAQ.md b/docs/FAQ.md index 25fe7aac8..ab82c8c75 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -32,7 +32,7 @@ If a setting is disabled with an amber warning, it's being overridden by an envi ## 🔍 Monitoring & Metrics ### What do Relay, Pro, and Cloud unlock? -Relay adds remote access, mobile access, push notifications, and 14-day history. Pro and Cloud unlock **alert-triggered root-cause analysis and safe remediation workflows** along with the broader operations feature set. Existing legacy Pro+ holders keep their current continuity, but self-hosted pricing no longer sells more monitoring volume. Pulse Patrol is available to everyone on Community with BYOK and provides scheduled, cross-system analysis that correlates real-time state, recent metrics history, and diagnostics to surface actionable findings. +Relay adds secure remote access to the Pulse web UI, mobile app pairing, push notifications, and 14-day history. Pro and Cloud unlock **alert-triggered root-cause analysis and safe remediation workflows** along with the broader operations feature set. Existing legacy Pro+ holders keep their current continuity, but self-hosted pricing no longer sells more monitoring volume. Pulse Patrol is available to everyone on Community with BYOK and provides scheduled, cross-system analysis that correlates real-time state, recent metrics history, and diagnostics to surface actionable findings. Example output includes trend-based capacity warnings, backup regressions, Kubernetes cluster analysis, and correlated container failures that simple threshold alerts miss. See [Pulse AI](AI.md), [Plans and entitlements](PULSE_PRO.md), and . diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md index 4a2a8e8dd..299f4759d 100644 --- a/docs/PRIVACY.md +++ b/docs/PRIVACY.md @@ -96,7 +96,7 @@ The telemetry implementation is in [`internal/telemetry/telemetry.go`](../intern Pulse can make outbound connections when you enable specific features: - **AI providers**: when AI features are configured, Pulse sends only the context required for your request to the provider you chose. Local providers stay on your network; non-local providers such as OpenAI or Anthropic receive provider-bound context directly from your Pulse instance. AI prompts from self-managed installs do not transit Pulse infrastructure. Before non-local model requests leave the instance, governed resource details use the same resource-policy redaction shown in Data Handling: local-only resource details are omitted from detailed prompt sections or replaced with policy-safe summaries, and known restricted resource identifiers are redacted where they appear in provider-bound context. See `docs/AI.md`. -- **Relay / Remote Access**: when relay is enabled, Pulse connects to the configured relay endpoint to enable mobile access. See Settings → Remote Access. +- **Relay / Remote Access**: when relay is enabled, Pulse connects to the configured relay endpoint to enable secure remote web access, mobile app pairing, and push notifications. See Settings → Remote Access. - **Update checks**: Pulse can check for new releases/updates (for example via GitHub release metadata) depending on your deployment and configuration. ### Local-only commercial handoff events diff --git a/docs/PULSE_PRO.md b/docs/PULSE_PRO.md index a37d58722..32cf1ad0a 100644 --- a/docs/PULSE_PRO.md +++ b/docs/PULSE_PRO.md @@ -32,8 +32,8 @@ Self-hosted pricing is: | Plan | Price | Core monitoring | Metric history | Purpose | |---|---:|---|---:|---| | Community | Free | Included | 7 days | Full self-hosted monitoring for normal homelab use | -| Relay | $4.99/mo or $39/yr | Included | 14 days | Remote access, mobile, push, and convenience | -| Pro | $8.99/mo or $79/yr | Included | 90 days | AI operations and advanced admin features | +| Relay | $39/yr or $4.99/mo | Included | 14 days | Remote web access, mobile pairing, push, and convenience | +| Pro | $79/yr or $8.99/mo | Included | 90 days | AI operations and advanced admin features | Counted examples: - Proxmox PVE node diff --git a/docs/README.md b/docs/README.md index 7b1377c58..fe8c1d0fa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -85,7 +85,7 @@ All other documents are supporting references unless explicitly required for evi Pulse is available in three self-hosted tiers plus hosted Cloud: - **Community**: Free self-hosted monitoring with core monitoring included and 7-day history. -- **Relay**: Adds remote access, mobile, push notifications, and 14-day history. +- **Relay**: Adds secure remote access to the Pulse web UI, mobile app pairing, push notifications, and 14-day history. - **Pro**: Adds alert-triggered root-cause analysis, safe remediation workflows, operations tooling, governance features, and 90-day history. - **Cloud**: Hosted Pulse with Pro-level capabilities; hosted pricing is unchanged by the self-hosted model lock. diff --git a/docs/release-control/v6/internal/records/self-hosted-commercial-model-lock-2026-03-17.md b/docs/release-control/v6/internal/records/self-hosted-commercial-model-lock-2026-03-17.md index 34c98c1e2..4587dce45 100644 --- a/docs/release-control/v6/internal/records/self-hosted-commercial-model-lock-2026-03-17.md +++ b/docs/release-control/v6/internal/records/self-hosted-commercial-model-lock-2026-03-17.md @@ -5,6 +5,12 @@ Target: `v6-rc-stabilization` Lanes: `L2`, `L13` Status: Superseded on 2026-04-16 by `self-hosted-core-monitoring-free`; this record now preserves the capped RC1 self-hosted model as historical context only and is not the final GA pricing direction. +Current replacement: Pulse v6 GA treats self-hosted core monitoring as included +for Community, Relay, and Pro. Relay is presented annual-first at `$39/year` with +`$4.99/month` as the secondary option, and it sells secure remote access to the +Pulse web UI, mobile app pairing, push notifications, and 14-day history rather +than monitored-system headroom or native mobile monitoring. + ## Historical Decision On 2026-03-17, Pulse v6 self-hosted commercial packaging was locked to this capped RC1 model: @@ -56,20 +62,11 @@ Rules: ## User-Facing Copy -Counted-unit explainer: - -> Pulse counts monitored systems, not everything underneath them. Each top-level machine or cluster counts once, no matter how Pulse collects it. VMs, containers, pods, disks, backups, and services under that system are included. - -Plan taglines: -- Community: Monitor up to 5 systems for free. -- Relay: Get a bit more room and monitor from anywhere. -- Pro: Pulse does not just watch your infrastructure. It helps operate it. -- Pro+: Everything in Pro, with more room for larger labs. - -Boundary-upgrade copy: -- Community to Relay: Need a little more room? Upgrade to Relay for 3 extra monitored systems plus remote access, mobile, and push notifications. -- Relay to Pro: Want Pulse to do more than alert? Upgrade to Pro for AI investigation, auto-fix, and 90-day history. -- Grace copy: Your existing monitoring will keep working for now, but new systems will not be added until you remove one or upgrade. +The capped RC1 copy that originally lived here is intentionally not retained as +reusable wording. Current user-facing copy must come from the replacement policy +above and from the shared self-hosted plan definitions, where self-hosted core +monitoring is included and Relay is positioned around remote web access, mobile +app pairing, push notifications, and 14-day history. ## Implementation Slices diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 4103bf93c..82a667b63 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -222,6 +222,13 @@ rather than legacy Community limit enforcement. Community as included core monitoring plus paid Relay/Pro extras. It must not frame self-hosted checkout as buying unlimited monitoring or monitored- system volume. + Relay pricing presentation should be annual-first at the existing public + $39/year price, with $4.99/month as the secondary option, so Relay reads as + an optional support/convenience tier rather than a pressure upgrade. Relay + copy must describe secure remote access to the Pulse web UI, mobile app + pairing, push notifications, and 14-day history; it must not imply that the + native mobile app is a full monitoring dashboard until that product surface + exists. 7. Add or change Stripe provisioning plan resolution through `internal/cloudcp/stripe/provisioner.go` 8. Add or change activation/grant lifecycle, release build helper gating, or dev-mode capability widening through `pkg/licensing/dev_mode_features.go`, `pkg/licensing/service.go`, `pkg/licensing/testing_helpers.go`, `pkg/licensing/grant_refresh.go`, and `pkg/licensing/revocation_poll.go` 9. Add or change license-server transport through `pkg/licensing/license_server_client.go` diff --git a/docs/releases/RELEASE_NOTES_v6.md b/docs/releases/RELEASE_NOTES_v6.md index b0e9c0bcb..9cae6e3e2 100644 --- a/docs/releases/RELEASE_NOTES_v6.md +++ b/docs/releases/RELEASE_NOTES_v6.md @@ -47,7 +47,7 @@ current public v6 plans. | Plan | Core monitoring | Metric history | Paid value | |---|---|---:|---| | Community | Included | 7 days | Full self-hosted monitoring | -| Relay | Included | 14 days | Remote access, mobile, push, and convenience | +| Relay | Included | 14 days | Remote web access, mobile app pairing, push, and convenience | | Pro | Included | 90 days | Relay plus AI operations, automation, and advanced admin features | Legacy `Pro+` remains continuity-only for existing holders. It is not a public diff --git a/docs/releases/RELEASE_NOTES_v6_RC2_DRAFT.md b/docs/releases/RELEASE_NOTES_v6_RC2_DRAFT.md index ee382ccfc..92ecf0ae7 100644 --- a/docs/releases/RELEASE_NOTES_v6_RC2_DRAFT.md +++ b/docs/releases/RELEASE_NOTES_v6_RC2_DRAFT.md @@ -36,7 +36,7 @@ Current self-hosted v6 packaging is: | Plan | Core monitoring | Metric history | Paid value | |---|---|---:|---| | Community | Included | 7 days | Full self-hosted monitoring | -| Relay | Included | 14 days | Remote access, mobile, push, and convenience | +| Relay | Included | 14 days | Remote web access, mobile app pairing, push, and convenience | | Pro | Included | 90 days | Relay plus AI operations, automation, and advanced admin features | Legacy `Pro+` remains continuity-only for existing holders. It is not a public diff --git a/docs/releases/V6_CHANGELOG_RC2_DRAFT.md b/docs/releases/V6_CHANGELOG_RC2_DRAFT.md index c2c0dac81..377917cf3 100644 --- a/docs/releases/V6_CHANGELOG_RC2_DRAFT.md +++ b/docs/releases/V6_CHANGELOG_RC2_DRAFT.md @@ -52,7 +52,7 @@ more monitored-system capacity. Instead, the self-hosted paid story is now consistently: - Relay: - remote access, mobile, push, and convenience + remote web access, mobile app pairing, push, and convenience - Pro: Relay plus AI operations, automation, governance, and longer history diff --git a/docs/releases/V6_RC2_OPERATOR_SUPPORT_PACK_DRAFT.md b/docs/releases/V6_RC2_OPERATOR_SUPPORT_PACK_DRAFT.md index 384dd389e..97bddcc85 100644 --- a/docs/releases/V6_RC2_OPERATOR_SUPPORT_PACK_DRAFT.md +++ b/docs/releases/V6_RC2_OPERATOR_SUPPORT_PACK_DRAFT.md @@ -40,7 +40,7 @@ inventory language, but they are no longer the self-hosted paid gate. - Community: core monitoring included, 7-day history - Relay: - Community plus remote access, mobile, push notifications, and 14-day history + Community plus remote web access, mobile app pairing, push notifications, and 14-day history - Pro: Relay plus AI operations, automation, advanced admin features, and 90-day history diff --git a/frontend-modern/public/docs/PRIVACY.md b/frontend-modern/public/docs/PRIVACY.md index 4a2a8e8dd..299f4759d 100644 --- a/frontend-modern/public/docs/PRIVACY.md +++ b/frontend-modern/public/docs/PRIVACY.md @@ -96,7 +96,7 @@ The telemetry implementation is in [`internal/telemetry/telemetry.go`](../intern Pulse can make outbound connections when you enable specific features: - **AI providers**: when AI features are configured, Pulse sends only the context required for your request to the provider you chose. Local providers stay on your network; non-local providers such as OpenAI or Anthropic receive provider-bound context directly from your Pulse instance. AI prompts from self-managed installs do not transit Pulse infrastructure. Before non-local model requests leave the instance, governed resource details use the same resource-policy redaction shown in Data Handling: local-only resource details are omitted from detailed prompt sections or replaced with policy-safe summaries, and known restricted resource identifiers are redacted where they appear in provider-bound context. See `docs/AI.md`. -- **Relay / Remote Access**: when relay is enabled, Pulse connects to the configured relay endpoint to enable mobile access. See Settings → Remote Access. +- **Relay / Remote Access**: when relay is enabled, Pulse connects to the configured relay endpoint to enable secure remote web access, mobile app pairing, and push notifications. See Settings → Remote Access. - **Update checks**: Pulse can check for new releases/updates (for example via GitHub release metadata) depending on your deployment and configuration. ### Local-only commercial handoff events diff --git a/frontend-modern/public/docs/README.md b/frontend-modern/public/docs/README.md index 7c699f97c..fe8c1d0fa 100644 --- a/frontend-modern/public/docs/README.md +++ b/frontend-modern/public/docs/README.md @@ -84,8 +84,8 @@ All other documents are supporting references unless explicitly required for evi Pulse is available in three self-hosted tiers plus hosted Cloud: -- **Community**: Free self-hosted core monitoring with 7-day history. -- **Relay**: Adds remote access, mobile, push notifications, and 14-day history. +- **Community**: Free self-hosted monitoring with core monitoring included and 7-day history. +- **Relay**: Adds secure remote access to the Pulse web UI, mobile app pairing, push notifications, and 14-day history. - **Pro**: Adds alert-triggered root-cause analysis, safe remediation workflows, operations tooling, governance features, and 90-day history. - **Cloud**: Hosted Pulse with Pro-level capabilities; hosted pricing is unchanged by the self-hosted model lock. diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx index 9d51b1ac5..7b21d2951 100644 --- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx @@ -270,7 +270,7 @@ describe('ProLicensePanel', () => { expect(screen.getByText('What Relay adds')).toBeInTheDocument(); expect(screen.getByText('What Pulse Pro adds')).toBeInTheDocument(); expect(screen.getByText('Pulse Relay (Remote Access)')).toBeInTheDocument(); - expect(screen.getByText('Mobile App Access')).toBeInTheDocument(); + expect(screen.getByText('Mobile App Pairing')).toBeInTheDocument(); expect(screen.getByText('Push Notifications')).toBeInTheDocument(); expect(screen.getByText('Alert Root-Cause Analysis')).toBeInTheDocument(); expect(screen.getByText('Safe Remediation Workflows')).toBeInTheDocument(); @@ -546,7 +546,7 @@ describe('ProLicensePanel', () => { expect( screen.getByText( - 'Relay is active on this instance. Remote access, mobile, push, and longer history are available right now.', + 'Relay is active on this instance. Remote web access, mobile pairing, push notifications, and longer history are available right now.', ), ).toBeInTheDocument(); expect(screen.queryByText('Optional extras')).not.toBeInTheDocument(); @@ -554,7 +554,7 @@ describe('ProLicensePanel', () => { expect(screen.queryByText('What Pulse Pro adds')).not.toBeInTheDocument(); expect(screen.queryByRole('link', { name: 'See all plans' })).not.toBeInTheDocument(); expect(screen.getByText('Pulse Relay (Remote Access)')).toBeInTheDocument(); - expect(screen.getByText('Mobile App Access')).toBeInTheDocument(); + expect(screen.getByText('Mobile App Pairing')).toBeInTheDocument(); expect(screen.getByText('Push Notifications')).toBeInTheDocument(); }); @@ -675,7 +675,9 @@ describe('ProLicensePanel', () => { expect(screen.queryByText('Why is continuity still pending?')).not.toBeInTheDocument(); expect(screen.queryByRole('tab', { name: 'Usage' })).not.toBeInTheDocument(); expect( - screen.queryByText('New top-level additions are paused until this legacy continuity state is reviewed.'), + screen.queryByText( + 'New top-level additions are paused until this legacy continuity state is reviewed.', + ), ).not.toBeInTheDocument(); }); @@ -731,9 +733,7 @@ describe('ProLicensePanel', () => { ), ).toBeInTheDocument(); expect(screen.queryByText('Legacy monitoring continuity')).not.toBeInTheDocument(); - expect( - screen.queryByText(/observed legacy estate available/i), - ).not.toBeInTheDocument(); + expect(screen.queryByText(/observed legacy estate available/i)).not.toBeInTheDocument(); expect(screen.queryByText('Legacy continuity')).not.toBeInTheDocument(); expect(screen.queryByText('23 monitored systems')).not.toBeInTheDocument(); expect(screen.queryByText('Monitored-system policy')).not.toBeInTheDocument(); @@ -793,7 +793,7 @@ describe('ProLicensePanel', () => { expect(screen.getByText('PDF/CSV Reporting')).toBeInTheDocument(); expect(screen.getByText('Centralized Agent Profiles')).toBeInTheDocument(); expect(screen.getByText('Pulse Relay (Remote Access)')).toBeInTheDocument(); - expect(screen.getByText('Mobile App Access')).toBeInTheDocument(); + expect(screen.getByText('Mobile App Pairing')).toBeInTheDocument(); expect(screen.getByText('Push Notifications')).toBeInTheDocument(); expect(screen.getByText('Extended Metric History')).toBeInTheDocument(); expect(screen.queryByText('Multi-Tenant Mode')).not.toBeInTheDocument(); diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index 82484b8ac..7fde08449 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -100,7 +100,7 @@ describe('licensePresentation', () => { it('returns canonical feature labels', () => { expect(getLicenseFeatureLabel('ai_patrol')).toBe('Pulse Patrol'); - expect(getLicenseFeatureLabel('mobile_app')).toBe('Mobile App Access'); + expect(getLicenseFeatureLabel('mobile_app')).toBe('Mobile App Pairing'); expect(getLicenseFeatureLabel('update_alerts')).toBe('Update Alerts'); expect(getLicenseFeatureLabel('relay')).toBe('Pulse Relay (Remote Access)'); expect(getLicenseFeatureLabel('custom_feature')).toBe('Custom Feature'); @@ -290,7 +290,7 @@ describe('licensePresentation', () => { }, displayableCapabilities: [ 'Pulse Relay (Remote Access)', - 'Mobile App Access', + 'Mobile App Pairing', 'Safe Remediation Workflows', ], }), @@ -393,10 +393,10 @@ describe('licensePresentation', () => { cards: [ { title: 'What Relay adds', - body: 'Reach this Pulse instance securely from anywhere, check it from mobile, get push notifications, and keep 14 days of history.', + body: 'Reach your Pulse web UI securely from anywhere, pair the mobile app for handoff and push notifications, and keep 14 days of history.', highlights: [ 'Pulse Relay (Remote Access)', - 'Mobile App Access', + 'Mobile App Pairing', 'Push Notifications', '14-day metric history', ], @@ -472,7 +472,7 @@ describe('licensePresentation', () => { }, displayableCapabilities: [ 'Pulse Relay (Remote Access)', - 'Mobile App Access', + 'Mobile App Pairing', 'Push Notifications', 'Safe Remediation Workflows', ], @@ -506,7 +506,7 @@ describe('licensePresentation', () => { }, displayableCapabilities: [ 'Pulse Relay (Remote Access)', - 'Mobile App Access', + 'Mobile App Pairing', 'Push Notifications', ], source: 'manual', @@ -518,7 +518,7 @@ describe('licensePresentation', () => { highlightsLabel: 'Available now on this instance', highlights: [ 'Pulse Relay (Remote Access)', - 'Mobile App Access', + 'Mobile App Pairing', 'Push Notifications', '14-day metric history', ], diff --git a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts index b68be7877..fbbc81520 100644 --- a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts +++ b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts @@ -52,7 +52,7 @@ describe('selfHostedPlans', () => { expect(SELF_HOSTED_COMMERCIAL_PRESENTATION).toEqual({ pageTitle: 'Pricing', pageDescription: - 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access and mobile convenience, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', + 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access to the Pulse web UI, mobile pairing, and push notifications, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', mostPopularBadge: 'Most Popular', currentPlanLabel: 'Current Plan', includedLabel: 'Included', @@ -94,17 +94,17 @@ describe('selfHostedPlans', () => { ]); expect(SELF_HOSTED_PLAN_BY_TIER.community.includedExtras).toEqual([]); expect(SELF_HOSTED_PLAN_BY_TIER.relay.billingExtrasSummary).toBe( - 'Remote access, mobile, and push', + 'Remote web access, mobile pairing, and push', ); expect(SELF_HOSTED_PLAN_BY_TIER.relay.entitlementSummary).toContain( - 'Remote access, mobile, push, and longer history are available right now.', + 'Remote web access, mobile pairing, push notifications, and longer history are available right now.', ); expect(SELF_HOSTED_PLAN_BY_TIER.relay.comparisonSummary).toContain( - 'Reach this Pulse instance securely from anywhere', + 'Reach your Pulse web UI securely from anywhere', ); expect(SELF_HOSTED_PLAN_BY_TIER.relay.entitlementHighlights).toEqual([ 'Pulse Relay (Remote Access)', - 'Mobile App Access', + 'Mobile App Pairing', 'Push Notifications', '14-day metric history', ]); diff --git a/frontend-modern/src/utils/licensePresentation.ts b/frontend-modern/src/utils/licensePresentation.ts index 1a1e6e95d..4a8b2cc2d 100644 --- a/frontend-modern/src/utils/licensePresentation.ts +++ b/frontend-modern/src/utils/licensePresentation.ts @@ -421,7 +421,7 @@ const getSelfHostedActivePlanSummary = ( case 'community': return `${planLabel} is active on this instance. It includes self-hosted monitoring, 7-day metric history, Pulse Patrol (BYOK), and update alerts.`; case 'relay': - return `${planLabel} is active on this instance. Remote access, mobile, push, and longer history are available right now.`; + return `${planLabel} is active on this instance. Remote web access, mobile pairing, push notifications, and longer history are available right now.`; case 'pro': return `${planLabel} is active on this instance. Root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras are available right now.`; default: diff --git a/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts b/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts index d8a299e39..3e33b2664 100644 --- a/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts +++ b/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts @@ -97,8 +97,8 @@ export const SELF_HOSTED_FEATURE_CATALOG: readonly GeneratedSelfHostedFeatureCat }, { "key": "mobile_app", - "displayName": "Mobile App Access", - "comparisonName": "Mobile App Access", + "displayName": "Mobile App Pairing", + "comparisonName": "Mobile App Pairing", "showInComparisonTable": true, "displayableInSelfHostedPlan": true, "roles": { diff --git a/frontend-modern/src/utils/selfHostedPlans.ts b/frontend-modern/src/utils/selfHostedPlans.ts index 2dd372b1d..934f27d46 100644 --- a/frontend-modern/src/utils/selfHostedPlans.ts +++ b/frontend-modern/src/utils/selfHostedPlans.ts @@ -108,20 +108,20 @@ export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] = { tier: 'relay', name: 'Relay', - price: '$4.99/month', - subline: 'or $39/year', + price: '$39/year', + subline: 'or $4.99/month', metricHistoryDays: 14, - billingExtrasSummary: 'Remote access, mobile, and push', + billingExtrasSummary: 'Remote web access, mobile pairing, and push', entitlementSummary: - 'Relay is active on this instance. Remote access, mobile, push, and longer history are available right now.', + 'Relay is active on this instance. Remote web access, mobile pairing, push notifications, and longer history are available right now.', entitlementHighlights: getTierEntitlementHighlights('relay', 14), includedExtras: [], comparisonSummary: - 'Reach this Pulse instance securely from anywhere, check it from mobile, get push notifications, and keep 14 days of history.', + 'Reach your Pulse web UI securely from anywhere, pair the mobile app for handoff and push notifications, and keep 14 days of history.', highlights: [ 'Everything in Community', - 'Remote access via Relay', - 'Mobile app access', + 'Remote web access via Relay', + 'Mobile app pairing', 'Push notifications', 'Custom URL (yourlab.pulserelay.pro)', '14-day metric history', @@ -130,8 +130,8 @@ export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] = { tier: 'pro', name: 'Pro', - price: '$8.99/month', - subline: 'or $79/year', + price: '$79/year', + subline: 'or $8.99/month', metricHistoryDays: 90, billingExtrasSummary: 'Analysis, remediation, and admin controls', entitlementSummary: @@ -179,7 +179,7 @@ export function getSelfHostedPlanDefinitionForBillingTier( export const SELF_HOSTED_COMMERCIAL_PRESENTATION: SelfHostedCommercialPresentation = { pageTitle: 'Pricing', pageDescription: - 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access and mobile convenience, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', + 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access to the Pulse web UI, mobile pairing, and push notifications, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', mostPopularBadge: 'Most Popular', currentPlanLabel: 'Current Plan', includedLabel: 'Included', diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json index ce628754d..fc6cde552 100644 --- a/internal/cloudcp/portal/dist/build_manifest.json +++ b/internal/cloudcp/portal/dist/build_manifest.json @@ -1,5 +1,5 @@ { - "source_hash": "48a3ee0ab24faeac280a149c1dbe673accd83ea6073ecf17afc7fc57255c503f", + "source_hash": "ff1828403441d25373e005de762dcfe0486e438e6b360d59fe217cbb22302906", "build_inputs": [ "package.json", "tsconfig.json", diff --git a/internal/cloudcp/portal/frontend/dev.mjs b/internal/cloudcp/portal/frontend/dev.mjs index a67141c4b..936bd9ca8 100644 --- a/internal/cloudcp/portal/frontend/dev.mjs +++ b/internal/cloudcp/portal/frontend/dev.mjs @@ -482,34 +482,34 @@ function routeCommercialAPI(request, response, url, scenario) { if (request.method === 'GET' && route === '/v1/public/pricing-model') { sendJSON(response, 200, { title: 'Simple self-hosted pricing for Pulse', - description: 'Community keeps core monitoring free. Relay lets you reach Pulse securely from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', + description: 'Community keeps core monitoring free. Relay gives secure remote access to the Pulse web UI, mobile app pairing, push notifications, and 14-day history. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', explainer: - 'Community keeps core monitoring free. Relay gets Pulse securely from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', + 'Community keeps core monitoring free. Relay gets your Pulse web UI securely reachable from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', plans: [ { badge: 'Recommended', highlight: true, tierKicker: 'Relay', title: 'Relay', - price: '$4.99', - period: '$39/year available too', - blurb: 'The reach-it-anywhere tier for secure remote access, mobile access, and push notifications.', + price: '$39/year', + period: 'or $4.99/month', + blurb: 'Secure remote access to your Pulse web UI, mobile app pairing, and push notifications.', features: [ { tone: 'check', html: 'Core self-hosted monitoring included' }, - { tone: 'check', html: 'Secure remote access' }, - { tone: 'check', html: 'Mobile access' }, + { tone: 'check', html: 'Secure remote web access' }, + { tone: 'check', html: 'Mobile app pairing' }, { tone: 'check', html: 'Push notifications' }, ], buttons: [ - { kind: 'checkout', className: 'btn btn-secondary', tier: 'relay', planKey: 'price_relay_monthly', billingCycle: 'monthly', label: 'Buy Monthly' }, { kind: 'checkout', className: 'btn btn-primary', tier: 'relay', planKey: 'price_relay_annual', billingCycle: 'annual', label: 'Buy Annual' }, + { kind: 'checkout', className: 'btn btn-secondary', tier: 'relay', planKey: 'price_relay_monthly', billingCycle: 'monthly', label: 'Buy Monthly' }, ], }, { tierKicker: 'Pro', title: 'Pro', - price: '$8.99', - period: '$79/year available too', + price: '$79/year', + period: 'or $8.99/month', blurb: 'The operator tier for root-cause analysis, safe remediation workflows, and 90-day history.', features: [ { tone: 'check', html: 'Core self-hosted monitoring included' }, @@ -519,8 +519,8 @@ function routeCommercialAPI(request, response, url, scenario) { { tone: 'check', html: '90-day history' }, ], buttons: [ - { kind: 'checkout', className: 'btn btn-secondary', tier: 'pro', planKey: 'price_pro_monthly', billingCycle: 'monthly', label: 'Buy Monthly' }, { kind: 'checkout', className: 'btn btn-primary', tier: 'pro', planKey: 'price_pro_annual', billingCycle: 'annual', label: 'Buy Annual' }, + { kind: 'checkout', className: 'btn btn-secondary', tier: 'pro', planKey: 'price_pro_monthly', billingCycle: 'monthly', label: 'Buy Monthly' }, ], }, ], diff --git a/internal/cloudcp/portal/frontend/src/billing_view.test.ts b/internal/cloudcp/portal/frontend/src/billing_view.test.ts index 8fe4a7a99..1b0da6c47 100644 --- a/internal/cloudcp/portal/frontend/src/billing_view.test.ts +++ b/internal/cloudcp/portal/frontend/src/billing_view.test.ts @@ -240,13 +240,13 @@ describe('services view', function() { title: 'Pricing', description: 'Canonical pricing model', explainer: - 'Community keeps core monitoring free. Relay gets Pulse securely from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', + 'Community keeps core monitoring free. Relay gets your Pulse web UI securely reachable from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', plans: [ { tierKicker: 'Pro', title: 'Pro', - price: '$8.99', - period: '$79/year available too', + price: '$79/year', + period: 'or $8.99/month', blurb: 'The operator tier for root-cause analysis, safe remediation workflows, and 90-day history.', features: [{ tone: 'check', html: 'Core self-hosted monitoring included' }], buttons: [ @@ -273,7 +273,7 @@ describe('services view', function() { 'Pulse Account keeps checkout tied to the Pulse instance that opened it, so completed Relay or Pro purchases return to the right Plans page automatically.', ); expect(document.getElementById('upgrade-billing-root')?.innerHTML).toContain( - 'Community keeps core monitoring free. Relay gets Pulse securely from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', + 'Community keeps core monitoring free. Relay gets your Pulse web UI securely reachable from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.', ); expect(document.getElementById('upgrade-billing-root')?.innerHTML).not.toMatch( /Unlimited[\s\S]{0,80}self-hosted monitoring/i, @@ -300,9 +300,9 @@ describe('services view', function() { { tierKicker: 'Relay', title: 'Relay', - price: '$4.99', - period: '$39/year available too', - blurb: 'Secure remote access and mobile access.', + price: '$39/year', + period: 'or $4.99/month', + blurb: 'Secure remote web access and mobile app pairing.', features: [{ tone: 'check', html: 'Core self-hosted monitoring included' }], buttons: [ { diff --git a/internal/license/coverage_additional_test.go b/internal/license/coverage_additional_test.go index f6b63aa3c..ec20eb157 100644 --- a/internal/license/coverage_additional_test.go +++ b/internal/license/coverage_additional_test.go @@ -92,7 +92,7 @@ func TestGetFeatureDisplayNameCoversKnownFeaturesAndFallback(t *testing.T) { {feature: FeatureSSO, want: "Basic SSO (OIDC)"}, {feature: FeatureAdvancedSSO, want: "Advanced SSO (SAML/Multi-Provider)"}, {feature: FeatureRelay, want: "Pulse Relay (Remote Access)"}, - {feature: FeatureMobileApp, want: "Mobile App Access"}, + {feature: FeatureMobileApp, want: "Mobile App Pairing"}, {feature: FeaturePushNotifications, want: "Push Notifications"}, {feature: FeatureAdvancedReporting, want: "PDF/CSV Reporting"}, {feature: FeatureLongTermMetrics, want: "Extended Metric History"}, diff --git a/pkg/licensing/features_test.go b/pkg/licensing/features_test.go index 279f67972..c8c861d49 100644 --- a/pkg/licensing/features_test.go +++ b/pkg/licensing/features_test.go @@ -237,7 +237,7 @@ func TestGetFeatureDisplayName(t *testing.T) { {FeatureSSO, "Basic SSO (OIDC)"}, {FeatureAdvancedSSO, "Advanced SSO (SAML/Multi-Provider)"}, {FeatureRelay, "Pulse Relay (Remote Access)"}, - {FeatureMobileApp, "Mobile App Access"}, + {FeatureMobileApp, "Mobile App Pairing"}, {FeaturePushNotifications, "Push Notifications"}, {FeatureAdvancedReporting, "PDF/CSV Reporting"}, {FeatureLongTermMetrics, "Extended Metric History"}, diff --git a/pkg/licensing/self_hosted_feature_catalog.go b/pkg/licensing/self_hosted_feature_catalog.go index 90e351e36..ba3f181ab 100644 --- a/pkg/licensing/self_hosted_feature_catalog.go +++ b/pkg/licensing/self_hosted_feature_catalog.go @@ -105,8 +105,8 @@ var featureMetadataCatalog = map[string]FeatureMetadata{ }, FeatureMobileApp: { Key: FeatureMobileApp, - DisplayName: "Mobile App Access", - ComparisonName: "Mobile App Access", + DisplayName: "Mobile App Pairing", + ComparisonName: "Mobile App Pairing", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ @@ -114,7 +114,7 @@ var featureMetadataCatalog = map[string]FeatureMetadata{ Relay: SelfHostedFeatureRolePrimaryPillar, Pro: SelfHostedFeatureRoleIncluded, }, - UpgradeReason: "Get Relay so you can check Pulse from your phone when you are away from the dashboard.", + UpgradeReason: "Get Relay so the mobile app can pair with this instance for secure handoff and notifications.", UpgradePriority: 1, }, FeaturePushNotifications: { diff --git a/tests/integration/tests/56-pulse-account-upgrade-bootstrap.spec.ts b/tests/integration/tests/56-pulse-account-upgrade-bootstrap.spec.ts index 3a8b522cd..1a44fdeee 100644 --- a/tests/integration/tests/56-pulse-account-upgrade-bootstrap.spec.ts +++ b/tests/integration/tests/56-pulse-account-upgrade-bootstrap.spec.ts @@ -78,11 +78,11 @@ test.describe("Pulse Account upgrade bootstrap", () => { "Pulse Account will return completed checkout directly to the Plans page in Pulse.", ), ).toBeVisible(); - await expect( - page.getByText( - "Community keeps monitoring free and unlimited. Relay gets Pulse securely from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.", - ), - ).toBeVisible(); + await expect( + page.getByText( + "Community keeps core monitoring free. Relay gets your Pulse web UI securely reachable from anywhere. Pro adds root-cause analysis, safe remediation workflows, and 90-day history.", + ), + ).toBeVisible(); await expect( page.getByText( "Pulse Account keeps checkout tied to the Pulse instance that opened it, so completed Relay or Pro purchases return to the right Plans page automatically.",