diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index c866d04df2..03a9c7ad7d 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -2340,6 +2340,13 @@ not a replacement status card, CTA band, or page-local nested card. disclosure; a non-expandable identity cell must otherwise expose its complete value without requiring hover. Default data rows stay single-line so scan density and row rhythm are not traded for automatic text wrapping. + A summary value that must truncate sits in a `block truncate` span as the + cell's only child, so it fills the fixed-layout column and ends in an + ellipsis inside it on the row's text line. A capped `inline-block max-w-* + truncate` span ignores the column width: it spills past a narrower cell + with no ellipsis, wastes room in a wider one, and sits about 2 pixels above + the row baseline. `platformOverviewLayout.guardrails.test.ts` holds the + Docker, Kubernetes, Proxmox and vSphere table sources to that rule. When a platform table row itself owns that keyboard- and touch-operable disclosure, `SummaryRowActionButton` removes the redundant visible chevron below the shared mobile breakpoint while preserving the button for screen diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index ed3f3e4803..dd5250c085 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -3094,6 +3094,17 @@ presentation and sort change only; it adds no backup, restore, or retention authority. Pinned by `proxmoxBackupRecoveryModel.test.ts` and `ProxmoxCoverageTable.test.tsx`. +### Backup table values truncate inside their own column + +The coverage Node cell, the restore-evidence Location and Details cells, and +the recoverable table's Location and Details cells render their value in a +block span that fills the fixed-layout cell, so a long node name, datastore path +or detail ends in an ellipsis inside its column on the row's text line, with the +full value on the title. A capped inline-block span let a long detail run past +its cell with no ellipsis and sat above the row. This is presentation only; it +adds no backup, restore, or retention authority. Pinned by +`ProxmoxCoverageTable.test.tsx` and `platformOverviewLayout.guardrails.test.ts`. + ### Retained Patrol objectives do not create recovery authority The shared `internal/api` retained-objective endpoints may scope an outcome to diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index b21e212e96..3ebdb085bb 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -1189,7 +1189,12 @@ RBAC names first, then the namespace scope, then the short Lifecycle and Data shape values, then Kind. Kubernetes summary cells truncate through a block span that fills the cell, so a long value ends in an ellipsis inside its own column on the row's text line; a fixed-width inline-block cap spilled past narrow -cells without an ellipsis and sat above the row baseline. +cells without an ellipsis and sat above the row baseline. Docker image, network, +volume, service, task, secret and config cells, Proxmox backup coverage, +restore-evidence and recoverable cells, the Mail Gateway drawer's top domains, +and vSphere host vCenter cells follow the same rule. The Docker service Update +cell still wraps on phones and from md truncates inside its column instead of +at a 10rem cap. Duration and interval cells keep the same split: unified-resource or source-specific consumers own which elapsed duration, human fallback, or poll interval field is meaningful, while dense platform table rendering must use diff --git a/frontend-modern/browser-verification/737e5f5d4595888b.json b/frontend-modern/browser-verification/737e5f5d4595888b.json new file mode 100644 index 0000000000..172f9537e5 --- /dev/null +++ b/frontend-modern/browser-verification/737e5f5d4595888b.json @@ -0,0 +1,90 @@ +{ + "version": 1, + "base_sha": "914fabfc1fef6d728bf145cf3097557ef82eafe7", + "verified_at": "2026-10-05T13:10:50Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/features/docker/DockerConfigsTable.tsx", + "frontend-modern/src/features/docker/DockerImagesTable.tsx", + "frontend-modern/src/features/docker/DockerNetworksTable.tsx", + "frontend-modern/src/features/docker/DockerSecretsTable.tsx", + "frontend-modern/src/features/docker/DockerServicesTable.tsx", + "frontend-modern/src/features/docker/DockerTasksTable.tsx", + "frontend-modern/src/features/docker/DockerVolumesTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx", + "frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx", + "frontend-modern/src/features/vmware/VsphereHostsTable.tsx" + ], + "content_sha256": { + "frontend-modern/src/features/docker/DockerConfigsTable.tsx": "5e5aa4ec47bff613fa1e61fffc6e5f78fbb52a11e74fd692f632f7d9f39c68ed", + "frontend-modern/src/features/docker/DockerImagesTable.tsx": "dad1a0a668160d0590d376d35da94f0e2e500965852631fff40e85a09ece62c1", + "frontend-modern/src/features/docker/DockerNetworksTable.tsx": "a4fbc6cfe58dabb7e453e0e65b6bc6aeb72bcc05945fb68a6571e503f44722c9", + "frontend-modern/src/features/docker/DockerSecretsTable.tsx": "3a68466cc647a84f4bc7cf9d9b6184ebf0476b9f21d05b9329ae400cfd9f7460", + "frontend-modern/src/features/docker/DockerServicesTable.tsx": "aa3cf92e9aeb611027cec10dc3596e81907732e20930c06ba5d9275451f42c1e", + "frontend-modern/src/features/docker/DockerTasksTable.tsx": "02435077ae71767e6f6db6bc0f3441a585661013dd62c35892e48229cc53e7b6", + "frontend-modern/src/features/docker/DockerVolumesTable.tsx": "020345e4bc6c9a4389410cbfbe566aa34dae8ea7c702f65246bdfb75ce9e3e96", + "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx": "b16eaab7cc00f13a858b290f453e46ee1886c1e50a9a542999f4568a0d42aead", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx": "18c6dbea9038cc6561f5f914f61e67954abc4a3a93f83560b76398ae2915b7ba", + "frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx": "f8f3695bbd200fa8a8f7091ec6431faa1dada5fbdbb98b80b9fc8c5f7ede304b", + "frontend-modern/src/features/vmware/VsphereHostsTable.tsx": "e6caa80836b45e9509d450e6816645062adc1cf6ce29041810e5daf70491ac6f" + }, + "routes": [ + "/docker/images", + "/docker/networks", + "/docker/storage", + "/docker/swarm", + "/proxmox/backups/date", + "/proxmox/backups/coverage", + "/proxmox/mail", + "/vmware/overview" + ], + "viewports": [ + { + "width": 390, + "height": 1000 + }, + { + "width": 600, + "height": 1000 + }, + { + "width": 700, + "height": 1000 + }, + { + "width": 768, + "height": 1000 + }, + { + "width": 900, + "height": 1000 + }, + { + "width": 1100, + "height": 1000 + }, + { + "width": 1280, + "height": 1000 + }, + { + "width": 1440, + "height": 1000 + } + ], + "states": [ + "Live DOM probe on main at 700 and 1440 over every changed span (Docker images, networks, volumes, services, tasks, secrets, configs; Proxmox recoverable, coverage Node and restore-evidence Location/Details, Mail Gateway drawer Top domains; vSphere hosts vCenter): all 1000 measured spans are the only child of their cell and every table, including the drawer's Top domains table, computes table-layout fixed", + "Main: 66 spans ran past their cell with no ellipsis (Service Image 225px and Secret Labels 184px at 700, Mountpoint 51px and restore-evidence Details 22px at 1440, PMG domains 71px at 700), and every inline-block span sat 2 to 2.5px above the row's text line", + "Patched: no span runs past its cell, all 134 truncated values end in an ellipsis inside their own column, and each value sits on the row's text line (0px; the 11px restore-evidence rows read 0.5px)", + "Docker service Update cell still wraps Rollback started onto two lines below md (700) and truncates inside its column from md (1440) instead of at a 10rem cap", + "Clip sweep (scrollWidth over clientWidth on every cell, screen-reader text excluded) across all eight routes at 390 to 1440: clipped cells 931 on main and 928 patched over 19209 measured cells, no column worse at any width (Secret Labels, Mountpoint and Used by each one better)", + "Before and after element screenshots of all eleven affected tables at 700 and 1440 show hard cuts such as registry.exa and mail-relay.customer-opera on main becoming registry... and mail-relay.customer-op... inside the cell, with Created and Mountpoint back on the row line", + "Mock PMG reports no domain stats, so the drawer's Top domains table was mounted by adding eight domains (4 to 54 characters) to the real /api/pmg/instances response through a Playwright route intercept" + ], + "interactions": [ + "Expanded the first three Proxmox coverage rows to mount the restore-evidence table", + "Opened the first Mail Gateway row's drawer to mount the Top domains table", + "Loaded each Docker, Proxmox backups (by date and coverage) and vSphere overview tab fresh at every viewport and waited for the row count to settle before measuring" + ] +} diff --git a/frontend-modern/src/features/docker/DockerConfigsTable.tsx b/frontend-modern/src/features/docker/DockerConfigsTable.tsx index 3ff681edc6..a3314ced4f 100644 --- a/frontend-modern/src/features/docker/DockerConfigsTable.tsx +++ b/frontend-modern/src/features/docker/DockerConfigsTable.tsx @@ -194,7 +194,7 @@ export const DockerConfigsTable: Component = (props) => class={`${getPlatformTableCellClassForKind('text')} text-base-content`} > = (props) => class={`${getPlatformTableCellClassForKind('text')} platform-table-phone-hidden text-base-content`} > {dockerLabelsSummary(resource.docker?.labels)} diff --git a/frontend-modern/src/features/docker/DockerImagesTable.tsx b/frontend-modern/src/features/docker/DockerImagesTable.tsx index 744bc18b71..454a028d53 100644 --- a/frontend-modern/src/features/docker/DockerImagesTable.tsx +++ b/frontend-modern/src/features/docker/DockerImagesTable.tsx @@ -221,10 +221,7 @@ export const DockerImagesTable: Component< - + {operational().consumerSummary} diff --git a/frontend-modern/src/features/docker/DockerNetworksTable.tsx b/frontend-modern/src/features/docker/DockerNetworksTable.tsx index db79aef671..8d6ff264a2 100644 --- a/frontend-modern/src/features/docker/DockerNetworksTable.tsx +++ b/frontend-modern/src/features/docker/DockerNetworksTable.tsx @@ -569,7 +569,7 @@ export const DockerNetworksTable: Component = (props) - + {subnets()} diff --git a/frontend-modern/src/features/docker/DockerSecretsTable.tsx b/frontend-modern/src/features/docker/DockerSecretsTable.tsx index 4e6c69465d..95a433a05e 100644 --- a/frontend-modern/src/features/docker/DockerSecretsTable.tsx +++ b/frontend-modern/src/features/docker/DockerSecretsTable.tsx @@ -209,7 +209,7 @@ export const DockerSecretsTable: Component = (props) => class={`${getPlatformTableCellClassForKind('text')} text-base-content`} > = (props) => class={`${getPlatformTableCellClassForKind('text')} platform-table-phone-hidden text-base-content`} > {dockerLabelsSummary(resource.docker?.labels)} diff --git a/frontend-modern/src/features/docker/DockerServicesTable.tsx b/frontend-modern/src/features/docker/DockerServicesTable.tsx index efb9d51923..596f0d8d22 100644 --- a/frontend-modern/src/features/docker/DockerServicesTable.tsx +++ b/frontend-modern/src/features/docker/DockerServicesTable.tsx @@ -331,14 +331,14 @@ export const DockerServicesTable: Component<{ - + {image()} @@ -361,7 +361,7 @@ export const DockerServicesTable: Component<{ class={`${getPlatformTableCellClassForKind('text')} text-base-content`} > {update().label} diff --git a/frontend-modern/src/features/docker/DockerTasksTable.tsx b/frontend-modern/src/features/docker/DockerTasksTable.tsx index e45f4a182b..10be8b87a6 100644 --- a/frontend-modern/src/features/docker/DockerTasksTable.tsx +++ b/frontend-modern/src/features/docker/DockerTasksTable.tsx @@ -238,7 +238,7 @@ export const DockerTasksTable: Component = (props) => { class={`${getPlatformTableCellClassForKind('text')} text-base-content`} > = (props) => class={`${getPlatformTableCellClassForKind('text')} text-base-content`} > = (props) => class={`${getPlatformTableCellClassForKind('text')} hidden text-base-content md:table-cell`} > {dockerTextValue(resource.docker?.mountpoint)} diff --git a/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts b/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts index 22a4c5545c..f55f91896a 100644 --- a/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts +++ b/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts @@ -395,11 +395,22 @@ describe('platform overview layout guardrails', () => { ); }); - it('keeps Kubernetes summary cells truncating inside their own column', () => { + it('keeps platform summary cells truncating inside their own column', () => { // A capped inline-block (max-w-52 truncate) ignored the column width, so a // long value spilled past a narrow cell without an ellipsis and sat above // the row's text line; a block span fills the cell and ellipsizes there. for (const source of [ + dockerConfigsTableSource, + dockerImagesTableSource, + dockerNetworksTableSource, + dockerSecretsTableSource, + dockerServicesTableSource, + dockerTasksTableSource, + dockerVolumesTableSource, + proxmoxCoverageTableSource, + proxmoxMailGatewayDrawerSource, + proxmoxRecoverableTableSource, + vsphereHostsTableSource, kubernetesAutoscalingTableSource, kubernetesConfigTableSource, kubernetesControllersTableSource, diff --git a/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx index 34233d7e81..8bce8e7698 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx @@ -350,7 +350,7 @@ export function ProxmoxCoverageTable(props: { fallback={—} > {(node) => ( - + {node()} )} @@ -586,10 +586,7 @@ export function ProxmoxCoverageTable(props: { - + {artifact.location} @@ -634,7 +631,7 @@ export function ProxmoxCoverageTable(props: { class={`${getPlatformTableCellClassForKind('text')} text-base-content`} > {artifact.detail || '—'} diff --git a/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx b/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx index 94390fc24f..bca5931740 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx @@ -649,7 +649,7 @@ export const ProxmoxMailGatewayDrawer: Component<{ - + {domain.domain || '—'} diff --git a/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx index f5a514a569..ff15869e3f 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxRecoverableTable.tsx @@ -160,7 +160,7 @@ export function ProxmoxRecoverableTable(props: { - + {artifact.location} @@ -192,10 +192,7 @@ export function ProxmoxRecoverableTable(props: { - + {artifact.detail || '—'} diff --git a/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx b/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx index a051faf0f4..87b2605ca3 100644 --- a/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx +++ b/frontend-modern/src/features/proxmox/__tests__/ProxmoxCoverageTable.test.tsx @@ -258,4 +258,32 @@ describe('ProxmoxCoverageTable column visibility', () => { ); expect(titles.join(' ')).not.toMatch(/canonical|provider evidence|independent recovery/); }); + + it('keeps a long node name truncating inside its own column', () => { + // A capped inline-block sat above the row's text line; a block span fills + // the fixed-layout cell and ends in an ellipsis there. + const node = 'pve-production-rack-04-node-17.example.internal'; + render(() => ( + } + emptyTitle="" + emptyDescription="" + sortKey={(() => 'posture') as Accessor} + sortDirection={() => 'asc'} + onSort={() => {}} + expandedKeys={new Set()} + onToggleExpand={() => {}} + showTaskColumn={false} + layoutWidth={() => 1_200} + /> + )); + + const span = document.querySelector(`td span[title="${node}"]`); + expect(span).toHaveTextContent(node); + expect(span?.parentElement?.children).toHaveLength(1); + expect(span?.getAttribute('class')).toBe('block truncate'); + expect(span?.closest('table')).toHaveClass('table-fixed'); + }); }); diff --git a/frontend-modern/src/features/vmware/VsphereHostsTable.tsx b/frontend-modern/src/features/vmware/VsphereHostsTable.tsx index aadbe5d836..965903f9a3 100644 --- a/frontend-modern/src/features/vmware/VsphereHostsTable.tsx +++ b/frontend-modern/src/features/vmware/VsphereHostsTable.tsx @@ -503,7 +503,7 @@ export const VsphereHostsTable: Component<{