Share Pulse Pro referral copy across settings surfaces

This commit is contained in:
rcourtman 2026-03-27 08:33:45 +00:00
parent fe7e47bc11
commit 7d032140a7
9 changed files with 45 additions and 14 deletions

View file

@ -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.

View file

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

View file

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

View file

@ -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<InfrastructureWorkspaceProps> =
connections, and control which infrastructure surfaces are actively reporting.
</p>
<p class="text-sm text-muted">
Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro,
not here.
{SELF_HOSTED_PRO_BILLING_PRESENTATION.infrastructureWorkspaceReferral}
</p>
</div>
</Card>

View file

@ -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',

View file

@ -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', () => {

View file

@ -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',

View file

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

View file

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