diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index c5823d1cbc..50344121e9 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -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 diff --git a/frontend-modern/browser-verification/0c24a36bf1559a9d.json b/frontend-modern/browser-verification/0c24a36bf1559a9d.json new file mode 100644 index 0000000000..74bf4631f9 --- /dev/null +++ b/frontend-modern/browser-verification/0c24a36bf1559a9d.json @@ -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" + ] +} diff --git a/frontend-modern/src/components/Workloads/__tests__/GuestRow.test.tsx b/frontend-modern/src/components/Workloads/__tests__/GuestRow.test.tsx index c269daff86..39b59fb197 100644 --- a/frontend-modern/src/components/Workloads/__tests__/GuestRow.test.tsx +++ b/frontend-modern/src/components/Workloads/__tests__/GuestRow.test.tsx @@ -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%', }); }); diff --git a/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx b/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx index 4217231ea1..d2aa75fff9 100644 --- a/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx +++ b/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx @@ -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); + }); +}); diff --git a/frontend-modern/src/components/Workloads/guestRowModel.tsx b/frontend-modern/src/components/Workloads/guestRowModel.tsx index 0c7c67ea17..df232cfa02 100644 --- a/frontend-modern/src/components/Workloads/guestRowModel.tsx +++ b/frontend-modern/src/components/Workloads/guestRowModel.tsx @@ -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[] = [ /> ), - 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,