Canonicalize monitored-system commercial copy

This commit is contained in:
rcourtman 2026-03-23 21:56:17 +00:00
parent b670cf2341
commit fa680da8dd
13 changed files with 48 additions and 19 deletions

View file

@ -224,6 +224,10 @@ show the counted monitored systems coming from agent-backed infrastructure, but
the shared API helper must expose the canonical unified-resource grouping
explanation instead of rebuilding count reasons from install or registration
state.
Lifecycle-adjacent workspace copy must also keep the same commercial framing:
infrastructure operations may point operators to Pulse Pro for billing, but it
must describe that boundary in monitored-system, plan-limit, and license-status
terms rather than reviving legacy agent-allocation language.
That same boundary now also assumes canonical resource payloads preserve
shared facet totals through `facetCounts`, so the resource list and detail
surfaces can keep row summaries aligned without re-inferring totals from

View file

@ -328,7 +328,13 @@ instead of redefining retail plan facts or counted-unit policy locally.
That same counted-unit boundary also owns the disclosure rule for retail copy:
default billing and pricing surfaces should use concise monitored-system copy,
while the full counted-unit definition appears only behind explicit disclosure
such as `What counts?` instead of sitting as always-visible explanatory chrome.
such as `View counting rules` instead of sitting as always-visible explanatory
chrome.
Those same billing-facing surfaces must also describe the commercial contract in
customer terms: monitored systems, plan limits, subscription status, and
license status. They must not revive legacy `installed-agent` wording or vague
internal nouns like `allocation` once the monitored-system billing model is the
canonical product truth.
The self-hosted pricing page should therefore stay focused on plan cards,
upgrade paths, and the comparison table rather than rendering a separate
counted-unit explainer card beneath the tier grid.

View file

@ -668,6 +668,10 @@ shared banner or shared settings shell is explaining self-hosted plan caps,
the operator-facing commercial term must follow the monitored-system model even
if explicit legacy-v5 compatibility helpers still decode older alias fields at
import boundaries.
That same settings-shell framing must stay in customer language. Shared headers
and descriptions should talk about monitored-system limits, plan limits, and
subscription or license status instead of reviving legacy `installed-agent`
terms or vague internal nouns like `allocation`.
That banner boundary now also owns the canonical monitored-system naming
surface directly: the shared warning component path and exported symbol are
`MonitoredSystemLimitWarningBanner`, and future work may not reintroduce an

View file

@ -10,7 +10,7 @@ describe('MonitoredSystemDefinitionDisclosure', () => {
));
expect(screen.getByText('Billing is based on monitored systems.')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'What counts?' })).toHaveAttribute(
expect(screen.getByRole('button', { name: 'View counting rules' })).toHaveAttribute(
'aria-expanded',
'false',
);
@ -18,9 +18,9 @@ describe('MonitoredSystemDefinitionDisclosure', () => {
screen.queryByText(/a monitored system is a top-level machine or cluster/i),
).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'What counts?' }));
fireEvent.click(screen.getByRole('button', { name: 'View counting rules' }));
expect(screen.getByRole('button', { name: 'Hide details' })).toHaveAttribute(
expect(screen.getByRole('button', { name: 'Hide counting rules' })).toHaveAttribute(
'aria-expanded',
'true',
);

View file

@ -33,8 +33,8 @@ export const InfrastructureWorkspace: Component<InfrastructureWorkspaceProps> =
connections, and control which infrastructure surfaces are actively reporting.
</p>
<p class="text-sm text-muted">
Billing, installed-agent allocation, and Pulse Pro entitlement state live in Pulse
Pro, not here.
Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro,
not here.
</p>
</div>
</Card>

View file

@ -15,7 +15,7 @@ export const ProLicensePanel: Component = () => {
<div class="space-y-6">
<CommercialBillingShell
title="Pulse Pro"
description="Manage self-hosted billing, monitored-system limits, and the activation state that controls paid features."
description="Manage self-hosted billing, monitored-system limits, and Pulse Pro license status."
icon={<ShieldCheck class="w-5 h-5" />}
action={
<button

View file

@ -58,7 +58,7 @@ describe('InfrastructureWorkspace', () => {
expect(screen.getByText('Infrastructure operations')).toBeInTheDocument();
expect(
screen.getByText(
'Billing, installed-agent allocation, and Pulse Pro entitlement state live in Pulse Pro, not here.',
'Billing, monitored-system limits, and Pulse Pro license status live in Pulse Pro, not here.',
),
).toBeInTheDocument();
expect(screen.getByRole('tab', { name: 'Install on a host' })).toHaveAttribute(

View file

@ -183,15 +183,15 @@ describe('MonitoredSystemLedgerPanel', () => {
expect(screen.getByText('Monitored System Ledger')).toBeInTheDocument();
expect(
screen.getByText(
'Review the monitored systems currently counting toward your Pulse Pro allocation.',
'Review the monitored systems currently counted against your Pulse Pro plan limit.',
),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'What counts?' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'View counting rules' })).toBeInTheDocument();
expect(
screen.queryByText(/a monitored system is a top-level machine or cluster pulse actively monitors/i),
).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'What counts?' }));
fireEvent.click(screen.getByRole('button', { name: 'View counting rules' }));
expect(
screen.getByText(/a monitored system is a top-level machine or cluster pulse actively monitors/i),

View file

@ -160,7 +160,7 @@ describe('ProLicensePanel', () => {
screen.queryByText(/a monitored system is a top-level machine or cluster/i),
).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'What counts?' }));
fireEvent.click(screen.getByRole('button', { name: 'View counting rules' }));
expect(
screen.getByText(/a monitored system is a top-level machine or cluster/i),

View file

@ -1039,6 +1039,21 @@ describe('Settings architecture guardrails', () => {
expect(SETTINGS_HEADER_META['infrastructure-operations'].description).toContain('Pulse Pro');
});
it('keeps billing-related shell framing on monitored-system commercial terms', () => {
expect(SETTINGS_HEADER_META['infrastructure-operations'].description).toContain(
'monitored-system limits',
);
expect(SETTINGS_HEADER_META['infrastructure-operations'].description).not.toContain(
'installed-agent',
);
expect(SETTINGS_HEADER_META['system-billing'].description).toContain('license status');
expect(SETTINGS_HEADER_META['system-billing'].description).not.toContain('allocation');
expect(SETTINGS_HEADER_META['organization-billing'].description).toContain(
'subscription status',
);
expect(SETTINGS_HEADER_META['organization-billing'].description).toContain('plan limits');
});
it('keeps shell titles aligned with the leading settings panel on key top-level surfaces', () => {
for (const { tab, title, source } of canonicalShellTitleExpectations) {
expect(SETTINGS_HEADER_META[tab].title, `${tab} should keep its shell title canonical`).toBe(

View file

@ -9,7 +9,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 installed-agent allocation live in Pulse Pro.',
'Bring infrastructure into Pulse, manage direct Proxmox connections, and control which systems are actively reporting. Billing and monitored-system limits live in Pulse Pro.',
},
'system-general': {
title: 'General',
@ -39,7 +39,7 @@ export const SETTINGS_HEADER_META: SettingsHeaderMetaMap = {
'system-billing': {
title: 'Pulse Pro',
description:
'Manage self-hosted Pulse Pro billing, installed-agent allocation, and the activation state that controls paid features.',
'Manage self-hosted Pulse Pro billing, monitored-system limits, and license status for paid features.',
},
'organization-overview': {
title: 'Organization Overview',
@ -56,7 +56,7 @@ export const SETTINGS_HEADER_META: SettingsHeaderMetaMap = {
'organization-billing': {
title: 'Billing & Usage',
description:
'Review your organization plan, usage against limits, and the allocation state that controls paid access.',
'Review your organization plan, usage against plan limits, and subscription status for paid access.',
},
'organization-billing-admin': {
title: 'Billing Admin',

View file

@ -68,7 +68,7 @@ describe('PricingV6', () => {
expect(
screen.queryByText('Billing is based on monitored systems. Child resources are included.'),
).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'What counts?' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'View counting rules' })).not.toBeInTheDocument();
});
it('imports the shared self-hosted pricing model instead of redefining it locally', () => {

View file

@ -22,15 +22,15 @@ export interface SelfHostedFeatureRow {
export const SELF_HOSTED_MONITORED_SYSTEMS_BRIEF =
'Billing is based on monitored systems. Child resources are included.';
export const SELF_HOSTED_MONITORED_SYSTEMS_DISCLOSURE_LABEL = 'What counts?';
export const SELF_HOSTED_MONITORED_SYSTEMS_DISCLOSURE_LABEL = 'View counting rules';
export const SELF_HOSTED_MONITORED_SYSTEMS_HIDE_LABEL = 'Hide details';
export const SELF_HOSTED_MONITORED_SYSTEMS_HIDE_LABEL = 'Hide counting rules';
export const SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION =
'A monitored system is a top-level machine or cluster Pulse actively monitors. Each system counts once no matter how Pulse collects it. Child resources like VMs, containers, pods, disks, backups, and services are included.';
export const SELF_HOSTED_MONITORED_SYSTEM_LEDGER_DESCRIPTION =
'Review the monitored systems currently counting toward your Pulse Pro allocation.';
'Review the monitored systems currently counted against your Pulse Pro plan limit.';
export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] = [
{