Fix Mac machine disk filtering

This commit is contained in:
rcourtman 2026-06-03 10:51:41 +01:00
parent c179afa89d
commit f868487a68
6 changed files with 110 additions and 20 deletions

View file

@ -232,8 +232,13 @@ export function StackedDiskBar(props: StackedDiskBarProps) {
</Show>
{/* Tooltip for disk breakdown */}
<TooltipPortal when={state.tooltipVisible()} x={state.tip.pos().x} y={state.tip.pos().y}>
<div class="min-w-[140px]">
<TooltipPortal
when={state.tooltipVisible()}
x={state.tip.pos().x}
y={state.tip.pos().y}
maxWidth={420}
>
<div class="w-[360px] max-w-full">
<div class="font-medium mb-1 text-slate-300 border-b border-border pb-1">
{presentation().tooltipTitle}
</div>
@ -243,15 +248,18 @@ export function StackedDiskBar(props: StackedDiskBarProps) {
class="flex flex-col gap-1 py-0.5"
classList={{ 'border-t border-border': idx() > 0 }}
>
<div class="flex justify-between gap-3">
<span class="flex max-w-[100px] items-center gap-1 truncate text-slate-300">
<svg aria-hidden="true" class="h-2 w-2 shrink-0" viewBox="0 0 8 8">
<div class="grid grid-cols-[minmax(0,1fr)_auto] gap-x-3 gap-y-0.5">
<span class="flex min-w-0 items-start gap-1.5 text-slate-300">
<svg aria-hidden="true" class="mt-0.5 h-2 w-2 shrink-0" viewBox="0 0 8 8">
<circle cx="4" cy="4" r="4" fill={item.color} />
</svg>
<span class="truncate">{item.label}</span>
<span class="min-w-0 break-all leading-snug">{item.label}</span>
</span>
<span class="whitespace-nowrap text-slate-300">
{item.percent} ({item.used}/{item.total})
<span class="whitespace-nowrap text-right font-medium text-base-content">
{item.percent}
</span>
<span class="col-span-2 text-right text-[9px] leading-none text-muted">
{item.used}/{item.total}
</span>
</div>
<div class="relative h-1.5 w-full overflow-hidden rounded bg-surface-hover">

View file

@ -318,6 +318,16 @@ describe('StackedDiskBar', () => {
expect(screen.getByText('Disk Breakdown')).toBeInTheDocument();
});
it('wraps long mountpoint labels in the tooltip instead of truncating them', async () => {
const longMountpoint = '/Volumes/Development/projects/pulse/cache/runtime-images';
const disk1 = makeDisk({ mountpoint: '/' });
const disk2 = makeDisk({ mountpoint: longMountpoint });
const { container } = render(() => <StackedDiskBar disks={[disk1, disk2]} />);
await fireEvent.mouseEnter(getBarTrigger(container));
expect(screen.getByText(longMountpoint)).toHaveClass('break-all');
});
it('hides tooltip on mouse leave', async () => {
const disk = makeDisk({ mountpoint: '/home' });
const { container } = render(() => <StackedDiskBar disks={[disk]} />);

View file

@ -165,4 +165,53 @@ describe('normalizeDiskArray', () => {
const result = normalizeDiskArray([{ device: '/dev/sda', mountpoint: '/home' }]);
expect(result?.[0].mountpoint).toBe('/home');
});
it('filters macOS APFS companion volumes and simulator runtime images', () => {
const result = normalizeDiskArray([
{
device: '/dev/disk3s1s1',
mountpoint: '/',
filesystem: 'apfs',
total: 245107195904,
used: 213573853184,
},
{
device: '/dev/disk8s1',
mountpoint: '/Library/Developer/CoreSimulator/Volumes/iOS_23C54',
filesystem: 'apfs',
total: 17645436928,
used: 17188392960,
},
{
device: '/dev/disk3s5',
mountpoint: '/System/Volumes/Data',
filesystem: 'apfs',
total: 245107195904,
used: 213573853184,
},
{
device: '/dev/disk7s1',
mountpoint: '/Volumes/Development',
filesystem: 'apfs',
total: 999995129856,
used: 204885471232,
},
]);
expect(result?.map((disk) => disk.mountpoint)).toEqual(['/', '/Volumes/Development']);
});
it('returns undefined when all disk rows are non-operational macOS mounts', () => {
expect(
normalizeDiskArray([
{
device: '/dev/disk3s2',
mountpoint: '/System/Volumes/Preboot',
filesystem: 'apfs',
total: 245107195904,
used: 213573853184,
},
]),
).toBeUndefined();
});
});

View file

@ -1,6 +1,27 @@
// Type-safe formatting utilities
import type { Disk } from '@/types/api';
type DiskInput = {
device?: string;
mountpoint?: string;
filesystem?: string;
type?: string;
total?: number;
used?: number;
free?: number;
};
const NON_OPERATIONAL_DISK_MOUNT_PREFIXES = [
'/System/Volumes/',
'/Library/Developer/CoreSimulator/Volumes/',
];
function shouldDisplayDisk(disk: DiskInput): boolean {
const mountpoint = disk.mountpoint?.trim() ?? '';
if (!mountpoint) return true;
return !NON_OPERATIONAL_DISK_MOUNT_PREFIXES.some((prefix) => mountpoint.startsWith(prefix));
}
/**
* Format bytes to human-readable string with dynamic precision.
* @param bytes - Number of bytes to format
@ -289,19 +310,9 @@ export function getShortImageName(fullImage: string | undefined): string {
* Normalize raw disk objects (from API/agent) into proper Disk[].
* Calculates `usage` from used/total and defaults missing fields.
*/
export function normalizeDiskArray(
disks?: Array<{
device?: string;
mountpoint?: string;
filesystem?: string;
type?: string;
total?: number;
used?: number;
free?: number;
}>,
): Disk[] | undefined {
export function normalizeDiskArray(disks?: DiskInput[]): Disk[] | undefined {
if (!disks || disks.length === 0) return undefined;
return disks.map((d) => {
const normalized = disks.filter(shouldDisplayDisk).map((d) => {
const total = d.total ?? 0;
const used = d.used ?? 0;
const free = d.free ?? (total > 0 ? Math.max(0, total - used) : 0);
@ -316,4 +327,5 @@ export function normalizeDiskArray(
device: d.device,
};
});
return normalized.length > 0 ? normalized : undefined;
}

View file

@ -99,6 +99,8 @@ var specialMountPrefixes = []string{
"/var/lib/docker",
"/var/lib/containers",
"/snap",
"/System/Volumes/", // macOS APFS system/data/preboot/update companion volumes
"/Library/Developer/CoreSimulator/Volumes/", // Xcode simulator runtime disk images
}
// containerOverlayPatterns detect container overlay filesystem paths from various
@ -124,6 +126,7 @@ var containerPathPrefixes = []string{
// should be excluded, along with a list of reason strings.
func ShouldSkipFilesystem(fsType, mountpoint string, totalBytes, usedBytes uint64) (skip bool, reasons []string) {
fsTypeLower := strings.ToLower(strings.TrimSpace(fsType))
mountpoint = strings.TrimSpace(mountpoint)
// Check read-only filesystems (existing logic)
if reason, isReadOnly := ReadOnlyFilesystemReason(fsType, totalBytes, usedBytes); isReadOnly {

View file

@ -194,6 +194,14 @@ func TestShouldSkipFilesystem(t *testing.T) {
{"Windows C drive - should NOT skip", "NTFS", "C:\\", 500 * 1024 * 1024 * 1024, 200 * 1024 * 1024 * 1024, false},
{"Windows D drive - should NOT skip", "NTFS", "D:\\", 1000 * 1024 * 1024 * 1024, 500 * 1024 * 1024 * 1024, false},
// macOS APFS companion volumes and simulator runtime images should not
// look like separate machine disks.
{"macos root APFS volume - should NOT skip", "apfs", "/", 245107195904, 213573853184, false},
{"macos external APFS volume - should NOT skip", "apfs", "/Volumes/Development", 999995129856, 204885471232, false},
{"macos data companion volume", "apfs", "/System/Volumes/Data", 245107195904, 213573853184, true},
{"macos preboot companion volume", "apfs", "/System/Volumes/Preboot", 245107195904, 213573853184, true},
{"macos simulator runtime volume", "apfs", "/Library/Developer/CoreSimulator/Volumes/iOS_23E254a", 18058575872, 17593331712, true},
// Regular filesystems that should NOT be skipped
{"ext4 root", "ext4", "/", 100 * 1024 * 1024 * 1024, 50 * 1024 * 1024 * 1024, false},
{"xfs data", "xfs", "/data", 500 * 1024 * 1024 * 1024, 200 * 1024 * 1024 * 1024, false},