Show macOS thermal pressure on Machines page

Render pressure-only thermal state in the standalone Machines temperature cell when no Celsius reading is available.
This commit is contained in:
rcourtman
2026-06-14 12:09:04 +01:00
parent b9d28a2f83
commit f3d4a8a5a6
3 changed files with 93 additions and 2 deletions
@@ -89,6 +89,7 @@ import {
getAgentMachineTemperatureCelsius,
getAgentMachineTemperatureDetailSections,
getAgentMachineTemperatureTitle,
getAgentMachineThermalPressurePresentation,
getNextAgentMachineSortState,
sortAgentMachines,
type AgentMachineColumn,
@@ -172,10 +173,16 @@ const AgentMachineTemperatureCell: Component<{
celsius: number | undefined;
sections: AgentMachineTemperatureDetailSection[];
title: string;
thermalPressure?: {
label: string;
title: string;
className: string;
};
}> = (props) => {
const hasDetails = () => props.sections.length > 0;
const positiveTemperature = () =>
hasPositiveTemperature(props.celsius) ? props.celsius : undefined;
const fallbackTitle = () => props.thermalPressure?.title ?? props.title;
return (
<AgentMachineMetricTooltip
@@ -184,10 +191,22 @@ const AgentMachineTemperatureCell: Component<{
triggerClass="inline-flex min-w-[2.25rem] justify-end text-xs tabular-nums"
tooltipClass="min-w-[190px] max-w-[300px] space-y-2"
enabled={hasDetails()}
ariaLabel={props.title || undefined}
ariaLabel={fallbackTitle() || undefined}
title={fallbackTitle()}
maxWidth={320}
trigger={
<Show when={positiveTemperature()} fallback={<span class="text-muted">—</span>}>
<Show
when={positiveTemperature()}
fallback={
<Show when={props.thermalPressure} fallback={<span class="text-muted">—</span>}>
{(thermal) => (
<span class={`text-[11px] font-medium ${thermal().className}`}>
{thermal().label}
</span>
)}
</Show>
}
>
{(value) => <TemperatureGauge value={value()} />}
</Show>
}
@@ -1465,6 +1484,8 @@ export const AgentsMachinesTable: Component<{
const temperatureSections = () =>
getAgentMachineTemperatureDetailSections(machine);
const temperatureTitle = () => getAgentMachineTemperatureTitle(machine);
const thermalPressure = () =>
getAgentMachineThermalPressurePresentation(machine);
const isExpanded = () => drawer.isExpanded(machine);
const detailRowId = () => drawer.detailRowId(machine);
const agentMetadataId = () => agentMetadataIdFor(machine);
@@ -1655,6 +1676,7 @@ export const AgentsMachinesTable: Component<{
celsius={temperature()}
sections={temperatureSections()}
title={temperatureTitle()}
thermalPressure={thermalPressure()}
/>
</TableCell>
</Show>
@@ -898,4 +898,38 @@ describe('AgentsMachinesTable', () => {
expect(screen.getByText('standby')).toBeInTheDocument();
expect(screen.getByText('1400 RPM')).toBeInTheDocument();
});
it('shows thermal pressure when macOS reports no Celsius temperature', async () => {
render(() => (
<AgentsMachinesTable
resources={[
resource({
id: 'mac-agent',
name: 'RICHARD-MAC-MINI.local',
temperature: undefined,
agent: {
agentVersion: 'v6.0.0',
sensors: {
thermalState: {
source: 'pmset',
pressure: 'nominal',
},
},
},
}),
]}
emptyIcon={emptyIcon}
emptyTitle="No machines"
emptyDescription="Install Pulse Agent."
/>
));
const pressure = await screen.findByText('Nominal');
expect(pressure).toBeInTheDocument();
expect(pressure.closest('[data-agent-machine-temperature-trigger="true"]')).toHaveAttribute(
'title',
'Thermal pressure nominal via pmset',
);
});
});
@@ -135,6 +135,12 @@ export type AgentMachineTemperatureDetailSection = {
rows: AgentMachineTemperatureDetailRow[];
};
export type AgentMachineThermalPressurePresentation = {
label: string;
title: string;
className: string;
};
export type AgentMachineNetworkInterfaceDetail = {
name: string;
mac?: string;
@@ -501,6 +507,35 @@ export const getAgentMachineTemperatureDetailSections = (
export const getAgentMachineTemperatureTitle = (machine: Resource): string =>
flattenTemperatureSections(getAgentMachineTemperatureDetailSections(machine));
export const getAgentMachineThermalPressurePresentation = (
machine: Resource,
): AgentMachineThermalPressurePresentation | undefined => {
const pressure = asTrimmedString(machine.agent?.sensors?.thermalState?.pressure)?.toLowerCase();
if (!pressure) return undefined;
const label =
pressure === 'nominal' ? 'Nominal' : pressure === 'constrained' ? 'Constrained' : 'Unknown';
const className =
pressure === 'nominal'
? 'text-emerald-600 dark:text-emerald-400'
: pressure === 'constrained'
? 'text-amber-600 dark:text-amber-400'
: 'text-muted';
const source = asTrimmedString(machine.agent?.sensors?.thermalState?.source);
const limits = Object.entries(machine.agent?.sensors?.thermalState?.limitsPercent ?? {})
.filter(([, value]) => Number.isFinite(value) && value < 100)
.sort(([left], [right]) => left.localeCompare(right))
.map(([key, value]) => `${key.replace(/_/g, ' ')} ${Math.round(value)}%`);
const sourceSummary = source ? ` via ${source}` : '';
const limitSummary = limits.length > 0 ? `; limits: ${limits.join(', ')}` : '';
return {
label,
className,
title: `Thermal pressure ${label.toLowerCase()}${sourceSummary}${limitSummary}`,
};
};
export const timestampMillisFrom = (
value: number | string | Date | undefined,
): number | undefined => {