From 7d032140a75308efe3ccfbe7f2928ce01a20ec3b Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 27 Mar 2026 08:33:45 +0000 Subject: [PATCH] Share Pulse Pro referral copy across settings surfaces --- .../v6/internal/subsystems/agent-lifecycle.md | 11 +++++------ .../v6/internal/subsystems/cloud-paid.md | 6 ++++++ .../v6/internal/subsystems/frontend-primitives.md | 12 +++++++++++- .../components/Settings/InfrastructureWorkspace.tsx | 4 ++-- .../__tests__/InfrastructureWorkspace.test.tsx | 5 ++--- .../Settings/__tests__/settingsArchitecture.test.ts | 11 ++++++++++- .../src/components/Settings/settingsHeaderMeta.ts | 2 +- .../src/utils/__tests__/licensePresentation.test.ts | 3 +++ frontend-modern/src/utils/licensePresentation.ts | 5 +++++ 9 files changed, 45 insertions(+), 14 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 1d328a0ba..00582f837 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -260,12 +260,6 @@ before any API-only token fallback or optional-auth anonymous fallback so operators can mint relay-mobile credentials and continue onboarding from the hosted runtime itself even after that tenant has already minted managed API tokens. -That same lifecycle-adjacent hosted setup path must also survive legacy tenant -runtime env drift. When Pulse Account hands an operator into a hosted workspace, -`internal/api/cloud_handoff_handlers.go` must still recover the canonical -tenant context from hosted runtime state if `PULSE_TENANT_ID` is missing, so -lifecycle entry into onboarding, setup, and mobile-pairing surfaces does not -die before the first authenticated page load. That same lifecycle-adjacent hosted setup path also depends on AI bootstrap staying canonical before the first settings write. Hosted operators may land in Chat, Patrol-backed setup hints, or AI-dependent remediation surfaces @@ -333,6 +327,11 @@ and `frontend-modern/src/utils/proxmoxSettingsPresentation.ts`, so endpoint reachability state, discovery-prefill defaults, and variant copy stay on the same governed lifecycle surface instead of drifting into card-local strings or prefill assembly. +When that infrastructure workspace needs to redirect operators to the Pulse Pro +surface for billing, monitored-system limits, or license status, it must +consume the shared referral copy from +`frontend-modern/src/utils/licensePresentation.ts` instead of carrying +workspace-local commercial guidance. That canonical /api/auto-register behavior now also includes hostname/IP continuity: reruns that arrive through a different canonical host form must reuse the same Pulse-managed node record and token instead of forking duplicate fleet entries. diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index d79919a95..6254576b5 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -489,6 +489,12 @@ The same title should also feed the `system-billing` settings navigation item, and the same title and description should also feed the `system-billing` route header metadata so the nav, shell, and route header do not narrate the same commercial surface differently. +That same shared presentation owner also carries the canonical cross-surface +referral copy used outside the billing shell itself. When infrastructure or +other adjacent settings surfaces need to point operators toward Pulse Pro for +billing, monitored-system limits, or license status, they must consume the +shared referral strings from `frontend-modern/src/utils/licensePresentation.ts` +instead of drafting route-local commercial guidance. 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/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 8eb5fcab6..796e78c32 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -157,7 +157,10 @@ work extends shared components instead of creating new local variants. 5. When a settings route header and a top-level settings shell describe the same commercial surface, keep them on the same shared presentation owner instead of allowing route metadata in `settingsHeaderMeta.ts` or labels in - `settingsNavCatalog.ts` to drift into independent title or description copy. + `settingsNavCatalog.ts` to drift into independent title or description copy, + and keep adjacent settings-shell referrals such as + `InfrastructureWorkspace.tsx` on that same shared owner instead of + reintroducing local “go to Pulse Pro” variants. ## Current State @@ -1255,6 +1258,13 @@ navigation label plus header title and description must reuse `SELF_HOSTED_PRO_BILLING_PRESENTATION.shellTitle` and `SELF_HOSTED_PRO_BILLING_PRESENTATION.shellDescription` so the route header and the billing shell do not narrate the same commercial surface differently. +That same settings-shell framing boundary also covers adjacent top-level +settings references to the self-hosted commercial surface. When +`InfrastructureWorkspace.tsx` or other settings-shell surfaces point operators +toward Pulse Pro for billing, monitored-system limits, or license status, they +must reuse the shared referral copy from +`SELF_HOSTED_PRO_BILLING_PRESENTATION` rather than drafting local “go there +for billing” variants. `frontend-modern/src/components/Settings/NetworkSettingsPanel.tsx` is now a shell only. `frontend-modern/src/components/Settings/NetworkDiscoverySection.tsx` owns discovery controls and shared subnet presets, while diff --git a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx index 87d02684e..4318680bd 100644 --- a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx +++ b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx @@ -2,6 +2,7 @@ import { Component, Match, Switch, createMemo } from 'solid-js'; import { useLocation, useNavigate } from '@solidjs/router'; import { Card } from '@/components/shared/Card'; import { Subtabs } from '@/components/shared/Subtabs'; +import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from '@/utils/licensePresentation'; import { InfrastructureInstallPanel } from './InfrastructureInstallPanel'; import { InfrastructureReportingPanel } from './InfrastructureReportingPanel'; import { ProxmoxSettingsPanel } from './ProxmoxSettingsPanel'; @@ -33,8 +34,7 @@ export const InfrastructureWorkspace: Component = connections, and control which infrastructure surfaces are actively reporting.

- Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro, - not here. + {SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral}

diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx index e71679a8b..adbf11644 100644 --- a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx @@ -1,5 +1,6 @@ import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from '@/utils/licensePresentation'; import { InfrastructureWorkspace } from '../InfrastructureWorkspace'; let mockPathname = '/settings'; @@ -57,9 +58,7 @@ describe('InfrastructureWorkspace', () => { expect(tablist).toBeInTheDocument(); expect(screen.getByText('Infrastructure operations')).toBeInTheDocument(); expect( - screen.getByText( - 'Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro, not here.', - ), + screen.getByText(SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral), ).toBeInTheDocument(); expect(screen.getByRole('tab', { name: 'Install on a host' })).toHaveAttribute( 'aria-selected', diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index 864f36cf3..7d6a3dae0 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -548,6 +548,13 @@ describe('Settings architecture guardrails', () => { expect(proLicensePanelSource).not.toContain('description="Manage self-hosted billing'); expect(proLicensePanelSource).not.toContain('title="Plan"'); expect(proLicensePanelSource).not.toContain('title="Usage"'); + expect(infrastructureWorkspaceSource).toContain('@/utils/licensePresentation'); + expect(infrastructureWorkspaceSource).toContain( + 'SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral', + ); + expect(infrastructureWorkspaceSource).not.toContain( + 'Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro, not here.', + ); expect(proLicensePlanSectionSource).toContain('CommercialStatGrid'); expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState'); expect(monitoredSystemPresentationSource).toContain( @@ -1266,7 +1273,9 @@ describe('Settings architecture guardrails', () => { expect(SETTINGS_HEADER_META['infrastructure-operations'].description).toContain( 'actively reporting', ); - expect(SETTINGS_HEADER_META['infrastructure-operations'].description).toContain('Pulse Pro'); + expect(SETTINGS_HEADER_META['infrastructure-operations'].description).toBe( + `Bring infrastructure into Pulse, manage direct Proxmox connections, and control which systems are actively reporting. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, + ); }); it('keeps billing-related shell framing on monitored-system commercial terms', () => { diff --git a/frontend-modern/src/components/Settings/settingsHeaderMeta.ts b/frontend-modern/src/components/Settings/settingsHeaderMeta.ts index bbfb0bf4a..5cf133081 100644 --- a/frontend-modern/src/components/Settings/settingsHeaderMeta.ts +++ b/frontend-modern/src/components/Settings/settingsHeaderMeta.ts @@ -10,7 +10,7 @@ export const SETTINGS_HEADER_META: SettingsHeaderMetaMap = { 'infrastructure-operations': { title: 'Infrastructure Operations', description: - 'Bring infrastructure into Pulse, manage direct Proxmox connections, and control which systems are actively reporting. Billing and monitored-system limits live in Pulse Pro.', + `Bring infrastructure into Pulse, manage direct Proxmox connections, and control which systems are actively reporting. ${SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureRouteReferral}`, }, 'system-general': { title: 'General', diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index 263a7c5d6..17497789b 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -66,6 +66,9 @@ describe('licensePresentation', () => { shellTitle: 'Pulse Pro', shellDescription: 'Manage self-hosted billing, monitored-system limits, and Pulse Pro license status.', + infrastructureRouteReferral: 'Billing and monitored-system limits live in Pulse Pro.', + infrastructureWorkspaceReferral: + 'Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro, not here.', refreshLabel: 'Refresh', planSectionTitle: 'Plan', planSectionDescription: diff --git a/frontend-modern/src/utils/licensePresentation.ts b/frontend-modern/src/utils/licensePresentation.ts index 7bcba57cc..0421829ea 100644 --- a/frontend-modern/src/utils/licensePresentation.ts +++ b/frontend-modern/src/utils/licensePresentation.ts @@ -105,6 +105,8 @@ export interface SelfHostedActivationPresentation { export interface SelfHostedProBillingPresentation { shellTitle: string; shellDescription: string; + infrastructureRouteReferral: string; + infrastructureWorkspaceReferral: string; refreshLabel: string; planSectionTitle: string; planSectionDescription: string; @@ -375,6 +377,9 @@ export const SELF_HOSTED_PRO_BILLING_PRESENTATION: SelfHostedProBillingPresentat shellTitle: 'Pulse Pro', shellDescription: 'Manage self-hosted billing, monitored-system limits, and Pulse Pro license status.', + infrastructureRouteReferral: 'Billing and monitored-system limits live in Pulse Pro.', + infrastructureWorkspaceReferral: + 'Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro, not here.', refreshLabel: 'Refresh', planSectionTitle: 'Plan', planSectionDescription: