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
- 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: