Keep the Proxmox guest Backup badge whole at laptop widths (#2525)

## Problem

The Proxmox guest table's Backup badge doesn't truncate, but its column
was too narrow for it.

- **Badge widths, with the shield:** Running 73px, Unknown 79px, None
58px, month ages ("12mo") 61px.
- **Column room:** the compact layout gave Backup a weight of 5, which
left a 33–58px content box from 1000 to 1513px. The wide layout gave it
72px.
- **Result:** Running and Unknown were cut inside their pill at every
width up to 1600px, and None and month ages up to about 1500px.

Running is the badge that says a backup is in progress.

## Change

In `guestRowModel.tsx`:

| Layout | Backup | Taken from | Why that column can give |
|---|---|---|---|
| Compact | weight 5 → 10 | Type 7 → 5.4 | It still fits the VM/LXC
badge and the sorted "TYPE▲" header (about 51px). |
| Compact | | CPU 13 → 9.6 | Its bar only shows a percentage and core
count. |
| Wide | 72px → 96px | CPU 140px → 116px | Same reason. |

Name, ID, Uptime, Net I/O, Disk I/O and the memory and disk bars keep
their widths, and their used/total figures. Views without a Backup
column (pods, Docker) give them slightly more, so two
`GuestRow.test.tsx` width expectations change. The peer whose claim
covers that file agreed to the edit; its own PR, #2524, edits a distant
hunk.

## Verification

Mock-mode private stack on main ebd6d09337, before against after, at 15
widths from 390 to 1920px:

- **Badge fit:** every Backup label fits at every width where the column
shows (1000–1920px). Main clipped Running and Unknown everywhere up to
1600px.
- **Clip sweep:** Proxmox goes from 464 to 452 clipped elements and
vSphere from 2 to 1. No column clips more at any width, and the memory
and disk used/total figures and the CPU core counts are unchanged in
every row.
- **Interactions:** clicking the Running badge opens the backup evidence
dialog at 1024, 1280 and 1600px, and Escape closes it. Row details open.
No console errors.
- **Tests:** a new check in
`WorkloadsSurface.performance.contract.test.tsx` covers the narrowest
compact table and the wide width, and fails against main's source. All
94 Workloads test files pass.

The browser receipt is
`frontend-modern/browser-verification/0c24a36bf1559a9d.json`.

**Known residual:** if a user enables an extra compact column, Backup's
share shrinks, and Unknown can clip again at the narrowest compact
width.
This commit is contained in:
rcourtman
2026-10-05 17:19:31 +01:00
committed by GitHub
5 changed files with 143 additions and 8 deletions
@@ -3766,6 +3766,18 @@ returns focus to the originating disclosure. Nested evidence activation must
not trigger the enclosing guest row. Touch targets are at least 44px; compact
desktop shields and the existing status colours remain.
The Backup column badge does not truncate. Running (73px with its shield) and
Unknown (79px) need about 95px with cell padding, so the compact layout weights
give Backup 10 against Type 5.4 and CPU 9.6, and the wide column is 96px with
the extra 24px taken from CPU (116px). Type still fits its VM/LXC badge and
sorted header, and CPU its percentage and core count, while Name, ID, Uptime,
Net I/O, Disk I/O and the memory and disk bars keep their widths and used/total
sublabels; views without a Backup column (pods, Docker) give them slightly more.
Before this, Running was cut inside its pill at every width up to 1600px, and
None and month ages up to about 1500px.
`WorkloadsSurface.performance.contract.test.tsx` checks the narrowest compact
table and the wide width.
The evidence content is reactive to the same guest identity: running is not
completion, malformed/future times are unknown, and only the existing absence
sentinels mean no backup recorded. A removed row unmounts its disclosure and
@@ -0,0 +1,90 @@
{
"version": 1,
"base_sha": "ebd6d09337d5e4cd17b327df5a69a58dae739a27",
"verified_at": "2026-10-05T15:39:31Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/components/Workloads/guestRowModel.tsx"
],
"content_sha256": {
"frontend-modern/src/components/Workloads/guestRowModel.tsx": "fa970cac1cde21056bc707a5ec6dc056d9f4dc9362f83a5bf8b59d5c2af1f75f"
},
"routes": [
"/proxmox/overview",
"/vmware/overview"
],
"viewports": [
{
"width": 390,
"height": 900
},
{
"width": 600,
"height": 900
},
{
"width": 768,
"height": 900
},
{
"width": 900,
"height": 900
},
{
"width": 973,
"height": 900
},
{
"width": 1000,
"height": 900
},
{
"width": 1024,
"height": 900
},
{
"width": 1100,
"height": 900
},
{
"width": 1180,
"height": 900
},
{
"width": 1280,
"height": 900
},
{
"width": 1366,
"height": 900
},
{
"width": 1440,
"height": 900
},
{
"width": 1513,
"height": 900
},
{
"width": 1600,
"height": 900
},
{
"width": 1920,
"height": 900
}
],
"states": [
"Private mock stack (PULSE_MOCK_MODE, random metrics off) serving this tree on main ebd6d09337; before = main, after = main plus this change, measured in the same session",
"Backup badge fit (each label cloned at natural width into the narrowest Backup cell): Running 73px, Unknown 79px, None 58px, 12mo 61px, 21d 52px with the shield. On main the column content box was 33-65px from 1000 to 1600 (compact layout weight 5, wide 72px), so Running and Unknown clipped at every width up to 1600 and None and month ages up to 1440-1513; patched the content box is 81-132px and every label fits at every width where the column shows (1000 to 1920)",
"Clip sweep over every cell of the Workloads table and its overflow-hidden or ellipsis descendants, .sr-only excluded, 15 widths 390 to 1920: Proxmox 464 to 452 and vSphere 2 to 1 clipped elements, no column clips more at any width, and the memory and disk used/total sublabels and the CPU core counts are unchanged in every row",
"Column widths patched at 1024: Name 259, Type 54, ID 70, CPU 96, Mem 140, Disk 140, Uptime 80, Backup 100 (main 259/70/70/130/140/140/80/50); at 1600 (wide) Name 224, CPU 130, Backup 108, Net I/O and Disk I/O 191 (main 224/157/81/191)",
"Before and after element screenshots of the guest table around the Running row at 1024, 1280 and 1600: Running cut inside its pill on main, whole patched"
],
"interactions": [
"Loaded each route fresh, waited for the row count to settle, resized through every viewport before measuring",
"Clicked the Running backup badge at 1024, 1280 and 1600: the Backup status evidence dialog opened (Backup running now\u2026) and Escape closed it",
"Clicked a guest row's name cell to open its details on both routes; no console errors"
]
}
@@ -1815,7 +1815,7 @@ describe('GUEST_COLUMNS', () => {
'aiContext',
]);
expect(getGuestColumnWidthStyle('name', false, 'compact', compactPodColumnIds)).toEqual({
width: '25%',
width: '25.8449%',
});
});
@@ -1834,7 +1834,7 @@ describe('GUEST_COLUMNS', () => {
expect(
getGuestColumnWidthStyle('update', false, 'compact', compactDockerRuntimeColumnIds),
).toEqual({
width: '8.9286%',
width: '9.2081%',
});
});
@@ -5,6 +5,7 @@ import { Route, Router } from '@solidjs/router';
import type { Resource } from '@/types/resource';
import { syncSessionSettingsCapabilities } from '@/stores/sessionSettingsCapabilities';
import { WorkloadsSurface } from '../WorkloadsSurface';
import { WORKLOAD_TABLE_CONTAINER_TABLET_WIDTH, getGuestColumnStyle } from '../guestRowModel';
import workloadsSource from '../WorkloadsSurface.tsx?raw';
import workloadsFilterSource from '../WorkloadsFilter.tsx?raw';
import workloadsWorkloadTableSource from '../WorkloadsTable.tsx?raw';
@@ -1870,3 +1871,26 @@ describe('Workloads performance contract', () => {
});
});
});
describe('Workloads Backup column fit', () => {
// The backup badge does not truncate: Running is 73px and Unknown 79px with
// the shield, plus 8px of cell padding a side. The compact layout starts at a
// 900px table, where the Proxmox guest columns all show.
const badgeWidth = 79 + 16;
it('keeps the widest backup badge whole at the narrowest compact table', () => {
const proxmoxColumns = ['name', 'type', 'vmid', 'cpu', 'memory', 'disk', 'uptime', 'backup'];
const percent = Number.parseFloat(
String(getGuestColumnStyle('backup', false, 'compact', proxmoxColumns)?.width),
);
expect((percent / 100) * WORKLOAD_TABLE_CONTAINER_TABLET_WIDTH).toBeGreaterThanOrEqual(
badgeWidth,
);
});
it('keeps the widest backup badge whole in the wide layout', () => {
expect(
Number.parseFloat(String(getGuestColumnStyle('backup', false, 'wide')?.width)),
).toBeGreaterThanOrEqual(badgeWidth);
});
});
@@ -241,7 +241,7 @@ export const GUEST_COLUMNS: ColumnDef[] = [
createVisibleCanonicalTypeColumn(),
{ id: 'info', label: 'Info', width: '100px', sortKey: 'info', kind: 'numeric-value' },
{ id: 'vmid', label: 'ID', width: '45px', sortKey: 'vmid', kind: 'numeric-value' },
{ id: 'cpu', label: 'CPU', width: '140px', sortKey: 'cpu', kind: 'metric-bar' },
{ id: 'cpu', label: 'CPU', width: '116px', sortKey: 'cpu', kind: 'metric-bar' },
{ id: 'memory', label: 'Mem', width: '140px', sortKey: 'memory', kind: 'metric-bar' },
{
id: 'disk',
@@ -411,8 +411,12 @@ export const GUEST_COLUMNS: ColumnDef[] = [
/>
</svg>
),
width: '72px',
minWidth: '68px',
// The badge does not truncate: Running is 73px and Unknown 79px with the
// shield, so the wide column keeps room for both with cell padding. Its
// extra 24px comes from CPU, whose bar shows only a percentage and core
// count, so the other wide columns keep their widths.
width: '96px',
minWidth: '90px',
toggleable: true,
kind: 'badge',
},
@@ -519,17 +523,22 @@ const GUEST_COLUMN_RESPONSIVE_WEIGHTS: Record<
memory: 17,
disk: 17,
},
// The Backup badge needs about 95px with cell padding for Unknown (79px)
// and Running (73px) at the narrowest compact table. Its room comes from Type, whose VM/LXC
// badge and sorted header need about 51px, and from CPU, whose bar shows
// only a percentage and core count; Name, ID, Uptime and the memory and
// disk bars keep their widths, so their used/total sublabels stay.
compact: {
name: 26,
runtime: 10,
type: 7,
type: 5.4,
info: 7,
vmid: 7,
cpu: 13,
cpu: 9.6,
memory: 14,
disk: 14,
uptime: 8,
backup: 5,
backup: 10,
image: 18,
namespace: 11,
context: 13,