Unify monitored system disclosure presentation

This commit is contained in:
rcourtman 2026-03-23 23:29:21 +00:00
parent c862ed3090
commit 24fa055ec1
11 changed files with 97 additions and 42 deletions

View file

@ -239,9 +239,11 @@ signal entirely, the settings surface should degrade to a safe customer-facing
fallback instead of an unexplained placeholder glyph.
That same billing support boundary now also owns the shared monitored-system
presentation helper. `frontend-modern/src/utils/monitoredSystemPresentation.ts`
is the canonical owner for ledger labels, safe fallback summaries, and
source/type attribution wording, so the settings panel must consume that
helper instead of redefining customer-facing monitored-system copy inline.
is the canonical owner for monitored-system brief/disclosure copy, ledger
labels, safe fallback summaries, and source/type attribution wording, so the
settings panel, Pro usage section, and counting-rules disclosure must consume
that helper instead of redefining customer-facing monitored-system copy inline
or keeping a parallel copy in generic self-hosted plan utilities.
Frontend billing/admin surfaces must not synthesize `plan_version` from
subscription lifecycle state. When a hosted billing record lacks a plan label,
the UI must preserve that absence instead of fabricating values like `active`

View file

@ -1,9 +1,8 @@
import { Show, createSignal, type Component } from 'solid-js';
import {
SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION,
SELF_HOSTED_MONITORED_SYSTEMS_DISCLOSURE_LABEL,
SELF_HOSTED_MONITORED_SYSTEMS_HIDE_LABEL,
} from '@/utils/selfHostedPlans';
getMonitoredSystemDisclosureDefinition,
getMonitoredSystemDisclosureToggleLabel,
} from '@/utils/monitoredSystemPresentation';
interface MonitoredSystemDefinitionDisclosureProps {
summary?: string;
@ -34,12 +33,12 @@ export const MonitoredSystemDefinitionDisclosure: Component<
aria-expanded={open()}
onClick={() => setOpen((current) => !current)}
>
{open() ? SELF_HOSTED_MONITORED_SYSTEMS_HIDE_LABEL : SELF_HOSTED_MONITORED_SYSTEMS_DISCLOSURE_LABEL}
{getMonitoredSystemDisclosureToggleLabel(open())}
</button>
<Show when={open()}>
<p class={props.detailClass ?? 'max-w-2xl text-xs text-muted'}>
{SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION}
{getMonitoredSystemDisclosureDefinition()}
</p>
</Show>
</div>

View file

@ -25,14 +25,12 @@ import { PulseLogoIcon } from '@/components/icons/PulseLogoIcon';
import {
formatMonitoredSystemLatestIncludedSignalSentence,
formatMonitoredSystemSurfaceAttribution,
getMonitoredSystemLedgerDescription,
getMonitoredSystemCountingDetailsToggleLabel,
getMonitoredSystemExplanationFallbackSummary,
getMonitoredSystemLedgerPresentation,
getMonitoredSystemStatusFallbackSummary,
} from '@/utils/monitoredSystemPresentation';
import {
SELF_HOSTED_MONITORED_SYSTEM_LEDGER_DESCRIPTION,
} from '@/utils/selfHostedPlans';
import { MonitoredSystemDefinitionDisclosure } from '@/components/Commercial/MonitoredSystemDefinitionDisclosure';
interface MonitoredSystemLedgerPanelProps {
@ -289,7 +287,7 @@ export function MonitoredSystemLedgerPanel(props: MonitoredSystemLedgerPanelProp
return (
<SettingsPanel
title={presentation.panelTitle}
description={SELF_HOSTED_MONITORED_SYSTEM_LEDGER_DESCRIPTION}
description={getMonitoredSystemLedgerDescription()}
icon={<PulseLogoIcon class="w-5 h-5" />}
bodyClass="space-y-4"
>

View file

@ -6,7 +6,7 @@ import { CommercialBillingShell, CommercialSection } from './CommercialBillingSe
import { ProLicensePlanSection } from './ProLicensePlanSection';
import { SelfHostedCommercialActivationSection } from './SelfHostedCommercialActivationSection';
import { useProLicensePanelState } from './useProLicensePanelState';
import { SELF_HOSTED_MONITORED_SYSTEMS_BRIEF } from '@/utils/selfHostedPlans';
import { getMonitoredSystemBriefSummary } from '@/utils/monitoredSystemPresentation';
export const ProLicensePanel: Component = () => {
const state = useProLicensePanelState();
@ -52,7 +52,7 @@ export const ProLicensePanel: Component = () => {
<CommercialSection
title="Usage"
description={SELF_HOSTED_MONITORED_SYSTEMS_BRIEF}
description={getMonitoredSystemBriefSummary()}
>
<MonitoredSystemLedgerPanel embedded />
</CommercialSection>

View file

@ -159,9 +159,17 @@ describe('ProLicensePanel', () => {
expect(
screen.queryByText(/a monitored system is a top-level machine or cluster/i),
).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: 'View counting rules' })).toHaveAttribute(
'aria-expanded',
'false',
);
fireEvent.click(screen.getByRole('button', { name: 'View counting rules' }));
expect(screen.getByRole('button', { name: 'Hide counting rules' })).toHaveAttribute(
'aria-expanded',
'true',
);
expect(
screen.getByText(/a monitored system is a top-level machine or cluster/i),
).toBeInTheDocument();

View file

@ -80,6 +80,7 @@ import relaySettingsPanelSource from '../RelaySettingsPanel.tsx?raw';
import relayPairingSectionSource from '../RelayPairingSection.tsx?raw';
import monitoredSystemLedgerPanelSource from '../MonitoredSystemLedgerPanel.tsx?raw';
import proLicensePanelSource from '../ProLicensePanel.tsx?raw';
import monitoredSystemDefinitionDisclosureSource from '@/components/Commercial/MonitoredSystemDefinitionDisclosure.tsx?raw';
import proLicensePlanSectionSource from '../ProLicensePlanSection.tsx?raw';
import commercialBillingSectionsSource from '../CommercialBillingSections.tsx?raw';
import selfHostedCommercialActivationSectionSource from '../SelfHostedCommercialActivationSection.tsx?raw';
@ -510,6 +511,15 @@ describe('Settings architecture guardrails', () => {
expect(monitoredSystemLedgerPanelSource).toContain('getMonitoredSystemCountingDetailsToggleLabel');
expect(monitoredSystemLedgerPanelSource).not.toContain('No monitored systems counted.');
expect(monitoredSystemLedgerPanelSource).not.toContain('Current status');
expect(monitoredSystemLedgerPanelSource).toContain('getMonitoredSystemLedgerDescription');
expect(proLicensePanelSource).toContain('@/utils/monitoredSystemPresentation');
expect(proLicensePanelSource).toContain('getMonitoredSystemBriefSummary');
expect(monitoredSystemDefinitionDisclosureSource).toContain(
'@/utils/monitoredSystemPresentation',
);
expect(monitoredSystemDefinitionDisclosureSource).toContain(
'getMonitoredSystemDisclosureToggleLabel',
);
expect(proLicensePanelStateSource).toContain('buildSelfHostedCommercialPlanModel');
expect(proLicensePanelStateSource).toContain('loadLicenseStatus(true)');
expect(proLicensePlanSectionSource).toContain('CommercialStatGrid');
@ -518,6 +528,12 @@ describe('Settings architecture guardrails', () => {
expect(monitoredSystemPresentationSource).toContain(
'export function getMonitoredSystemCountingDetailsToggleLabel',
);
expect(monitoredSystemPresentationSource).toContain(
'export function getMonitoredSystemBriefSummary',
);
expect(monitoredSystemPresentationSource).toContain(
'export function getMonitoredSystemDisclosureToggleLabel',
);
expect(selfHostedCommercialActivationSectionSource).toContain('License / Activation Key');
expect(selfHostedCommercialActivationSectionSource).toContain('Start 14-day Pro Trial');
expect(organizationBillingPanelSource).toContain('./CommercialBillingSections');

View file

@ -61,6 +61,7 @@ import trialBannerModelSource from '@/components/shared/trialBannerModel.ts?raw'
import monitoredSystemLimitWarningBannerSource from '@/components/shared/MonitoredSystemLimitWarningBanner.tsx?raw';
import monitoredSystemLimitWarningBannerModelSource from '@/components/shared/monitoredSystemLimitWarningBannerModel.ts?raw';
import monitoredSystemLedgerPanelSource from '@/components/Settings/MonitoredSystemLedgerPanel.tsx?raw';
import monitoredSystemDefinitionDisclosureSource from '@/components/Commercial/MonitoredSystemDefinitionDisclosure.tsx?raw';
import infrastructureSummaryTableSource from '@/components/shared/InfrastructureSummaryTable.tsx?raw';
import infrastructureSummaryTableRowSource from '@/components/shared/InfrastructureSummaryTableRow.tsx?raw';
import interactiveSparklineSource from '@/components/shared/InteractiveSparkline.tsx?raw';
@ -3001,8 +3002,15 @@ describe('frontend resource type boundaries', () => {
expect(monitoredSystemLedgerPanelSource).toContain(
'getMonitoredSystemCountingDetailsToggleLabel',
);
expect(monitoredSystemLedgerPanelSource).toContain('getMonitoredSystemLedgerDescription');
expect(monitoredSystemLedgerPanelSource).not.toContain('No monitored systems counted.');
expect(monitoredSystemLedgerPanelSource).not.toContain('Current status');
expect(monitoredSystemDefinitionDisclosureSource).toContain(
'@/utils/monitoredSystemPresentation',
);
expect(monitoredSystemDefinitionDisclosureSource).toContain(
'getMonitoredSystemDisclosureToggleLabel',
);
expect(monitoredSystemPresentationSource).toContain(
'export function getMonitoredSystemLedgerPresentation',
);
@ -3012,6 +3020,12 @@ describe('frontend resource type boundaries', () => {
expect(monitoredSystemPresentationSource).toContain(
'export function formatMonitoredSystemLatestIncludedSignalSentence',
);
expect(monitoredSystemPresentationSource).toContain(
'export function getMonitoredSystemBriefSummary',
);
expect(monitoredSystemPresentationSource).toContain(
'export function getMonitoredSystemDisclosureToggleLabel',
);
expect(whatsNewModalSource).toContain('useWhatsNewModalState');
expect(whatsNewModalSource).toContain('WHATS_NEW_FEATURE_CARDS');
expect(whatsNewModalSource).not.toContain('createLocalStorageBooleanSignal');

View file

@ -1,10 +1,14 @@
import { describe, expect, it } from 'vitest';
import {
getMonitoredSystemBriefSummary,
formatMonitoredSystemLatestIncludedSignalSentence,
formatMonitoredSystemSurfaceAttribution,
getMonitoredSystemCountingDetailsToggleLabel,
getMonitoredSystemDisclosureDefinition,
getMonitoredSystemDisclosureToggleLabel,
getMonitoredSystemExplanationFallbackSummary,
getMonitoredSystemLedgerDescription,
getMonitoredSystemLedgerPresentation,
getMonitoredSystemSourceLabel,
getMonitoredSystemStatusFallbackSummary,
@ -14,8 +18,15 @@ import {
describe('monitoredSystemPresentation', () => {
it('returns canonical ledger labels and fallback copy', () => {
expect(getMonitoredSystemLedgerPresentation()).toEqual({
briefSummary: 'Billing is based on monitored systems. Child resources are included.',
sectionTitle: 'Monitored Systems',
panelTitle: 'Monitored System Ledger',
disclosureButtonLabel: 'View counting rules',
disclosureHideLabel: 'Hide counting rules',
disclosureDefinition:
'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.',
ledgerDescription:
'Review the monitored systems currently counted against your Pulse Pro plan limit.',
tableNameLabel: 'Name',
tableStatusLabel: 'Status',
tableLatestIncludedSignalLabel: 'Latest Included Signal',
@ -30,6 +41,15 @@ describe('monitoredSystemPresentation', () => {
fallbackStatusSummary:
'Pulse cannot determine a canonical runtime status for this monitored system yet.',
});
expect(getMonitoredSystemBriefSummary()).toBe(
'Billing is based on monitored systems. Child resources are included.',
);
expect(getMonitoredSystemDisclosureToggleLabel(false)).toBe('View counting rules');
expect(getMonitoredSystemDisclosureToggleLabel(true)).toBe('Hide counting rules');
expect(getMonitoredSystemDisclosureDefinition()).toContain('top-level machine or cluster');
expect(getMonitoredSystemLedgerDescription()).toBe(
'Review the monitored systems currently counted against your Pulse Pro plan limit.',
);
expect(getMonitoredSystemCountingDetailsToggleLabel(false)).toBe('View counting details');
expect(getMonitoredSystemCountingDetailsToggleLabel(true)).toBe('Hide counting details');
expect(getMonitoredSystemExplanationFallbackSummary()).toBe(

View file

@ -2,25 +2,11 @@ import { describe, expect, it } from 'vitest';
import {
SELF_HOSTED_FEATURE_ROWS,
SELF_HOSTED_MONITORED_SYSTEMS_BRIEF,
SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION,
SELF_HOSTED_PLAN_BY_TIER,
SELF_HOSTED_PLAN_DEFINITIONS,
} from '../selfHostedPlans';
describe('selfHostedPlans', () => {
it('keeps the monitored-system wording concise by default and explicit on disclosure', () => {
expect(SELF_HOSTED_MONITORED_SYSTEMS_BRIEF).toBe(
'Billing is based on monitored systems. Child resources are included.',
);
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain(
'top-level machine or cluster',
);
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain('counts once');
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain('VMs');
expect(SELF_HOSTED_MONITORED_SYSTEMS_DEFINITION).toContain('services');
});
it('keeps self-hosted plan limits aligned across tier cards and comparison rows', () => {
expect(SELF_HOSTED_PLAN_DEFINITIONS.map((tier) => tier.name)).toEqual([
'Community',

View file

@ -9,8 +9,15 @@ const titleCaseWords = (value: string): string =>
.join(' ');
const MONITORED_SYSTEM_LEDGER_PRESENTATION = {
briefSummary: 'Billing is based on monitored systems. Child resources are included.',
sectionTitle: 'Monitored Systems',
panelTitle: 'Monitored System Ledger',
disclosureButtonLabel: 'View counting rules',
disclosureHideLabel: 'Hide counting rules',
disclosureDefinition:
'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.',
ledgerDescription:
'Review the monitored systems currently counted against your Pulse Pro plan limit.',
tableNameLabel: 'Name',
tableStatusLabel: 'Status',
tableLatestIncludedSignalLabel: 'Latest Included Signal',
@ -30,6 +37,24 @@ export function getMonitoredSystemLedgerPresentation() {
return MONITORED_SYSTEM_LEDGER_PRESENTATION;
}
export function getMonitoredSystemBriefSummary(): string {
return MONITORED_SYSTEM_LEDGER_PRESENTATION.briefSummary;
}
export function getMonitoredSystemDisclosureToggleLabel(open: boolean): string {
return open
? MONITORED_SYSTEM_LEDGER_PRESENTATION.disclosureHideLabel
: MONITORED_SYSTEM_LEDGER_PRESENTATION.disclosureButtonLabel;
}
export function getMonitoredSystemDisclosureDefinition(): string {
return MONITORED_SYSTEM_LEDGER_PRESENTATION.disclosureDefinition;
}
export function getMonitoredSystemLedgerDescription(): string {
return MONITORED_SYSTEM_LEDGER_PRESENTATION.ledgerDescription;
}
export function getMonitoredSystemCountingDetailsToggleLabel(expanded: boolean): string {
return expanded
? MONITORED_SYSTEM_LEDGER_PRESENTATION.countingDetailsExpandedLabel

View file

@ -19,19 +19,6 @@ export interface SelfHostedFeatureRow {
proPlus: boolean | string;
}
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 = 'View counting rules';
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 counted against your Pulse Pro plan limit.';
export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] = [
{
tier: 'community',