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: