From dfd6b4bd1911fbc04e7d25fd18ce45fbe26463b1 Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:22:28 +0100 Subject: [PATCH] Keep TrueNAS storage rows single-line, reason in Health The first version put the reason on a second line under the name. The shared platform-table contract forbids that: summary rows keep one line on a 32px rhythm, and context belongs in an operational column, a tooltip or the row drawer. The taller rows also needed a windowing change to keep the spacers aligned. The reason now sits in the desktop Health column on the row's single line, coloured by bucket, with a count for the rest and every reason on hover and for screen readers. Phones keep the status dot and reach the reason through the row drawer. The windowing change is reverted, since rows are uniform again and it no longer had a case to serve. --- .../subsystems/frontend-primitives.md | 9 +- .../subsystems/performance-and-scalability.md | 14 ++-- .../internal/subsystems/unified-resources.md | 7 +- .../24363d2e58468e62.json | 45 ++++++++++ .../__tests__/PlatformWindowedRows.test.tsx | 59 ------------- .../platformPage/usePlatformWindowedItems.ts | 41 ++------- .../truenas/TrueNASStorageTopologyTable.tsx | 84 +++++++------------ .../TrueNASStorageTopologyTable.test.tsx | 18 ++-- 8 files changed, 103 insertions(+), 174 deletions(-) create mode 100644 frontend-modern/browser-verification/24363d2e58468e62.json diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index ab0525f836..290a92b001 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -663,8 +663,8 @@ On narrow screens the physical-disk scope may reprioritize its canonical table columns to endurance and temperature while retaining the shared table overflow and touch-target behavior; it must not introduce a second mobile-only filter or table shell. Its Health column stays desktop-only in every scope: -the status dot plus the reason line under the name already carry a disk's -condition, and the pill clipped past the phone container edge. +the status dot carries a disk's condition on a phone, the row drawer carries +the reason, and the old pill clipped past the phone container edge. Large-estate platform pages must keep one canonical inventory snapshot for the initial read and explicit refresh path. The Proxmox and VMware vSphere @@ -713,10 +713,7 @@ scrolling exposes it. Touch scrolling must remain compositor-native: windowed renderers must not attach touch listeners or replace keyed rows before the browser moves the page, and must update their runway only from the passive native scroll event. Spacer geometry is structural only: no feature may present -it as loading, pagination, or an intentionally blank data region. Table rows -may differ in height (an exception row with a reason line under its name), -because a table runway sizes its spacers from the average mounted row rather -than its tallest leading row. Settings +it as loading, pagination, or an intentionally blank data region. Settings resource pickers, Availability target lists, Actions, alerts, and every provider-native platform table share this contract. For table rows with unique logical ids, or an explicit unique key extractor, `PlatformWindowedRows` owns a 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 bb4d490869..1a49ebcb1e 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -2555,15 +2555,11 @@ sparse live snapshots update fields without remounting an open inline drawer or discarding its tab and form state. Rows without a unique logical ID retain reference-keyed rendering rather than paying for guessed index identity. Spacer geometry and the inverse scroll-to-index mapping must share one -representative item height. A table runway, anchored on its top spacer row, -measures every mounted row up to the bottom spacer and divides by the mounted -item count, so a few taller rows (a reason line under an exception's name, an -open detail row) count once instead of sizing every unmounted row and making -the window jump as they scroll out. Other anchors sample several leading -siblings and keep the tallest, because a short leading group header sampled -alone collapses the estimate and lets the mounted window outrun the real scroll -position, turning a small scroll into many rows of unmounting and making an -estate appear to lose rows. +representative item height. The controller samples several leading siblings and +keeps the tallest, because a short leading group header sampled alone collapses +the estimate and lets the mounted window outrun the real scroll position, +turning a small scroll into many rows of unmounting and making an estate appear +to lose rows. The workload guest-row path now follows the same pattern: the render shell stays in `frontend-modern/src/components/Workloads/GuestRow.tsx`, tooltip-backed cell presentation lives in `frontend-modern/src/components/Workloads/GuestRowCells.tsx`, diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 4326ce42f2..95d593c3f3 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -968,9 +968,10 @@ resource, storage and physical-disk risk summaries, then the dataset state tag (`state:locked`, `state:unmounted`, `state:readonly`, which is the only place the provider says why a dataset is impaired). Without any of those it falls back to the pool state, the disk health value, or an impaired TrueNAS source. -The first reason renders under the name on every width and the full list stays -in the title, so a phone user learns why a row is amber without the Health -column. Health sorts by severity rank, not label text. +The first reason renders in the desktop Health column on the row's single line, +coloured by bucket, with a count for the rest and the full list in the title +and screen-reader text. Phones keep the status dot and reach the reason through +the row drawer. Health sorts by severity rank, not label text. That shell also owns responsive width composition. Source tables may declare desktop breakpoint floors, but the phone branch must fit its selected columns inside the available container without a horizontal rail. Each unified-resource diff --git a/frontend-modern/browser-verification/24363d2e58468e62.json b/frontend-modern/browser-verification/24363d2e58468e62.json new file mode 100644 index 0000000000..78aa7affb2 --- /dev/null +++ b/frontend-modern/browser-verification/24363d2e58468e62.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "base_sha": "a99f3804a756acc293b3962fb9eaba2cbec04655", + "verified_at": "2026-10-05T05:22:24Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/features/platformPage/usePlatformWindowedItems.ts", + "frontend-modern/src/features/truenas/TrueNASStorageTopologyTable.tsx" + ], + "content_sha256": { + "frontend-modern/src/features/platformPage/usePlatformWindowedItems.ts": "e1cc0cb0f4087c5c66014d254421aeea6b02842795c41978e1b9847dcfcb5c77", + "frontend-modern/src/features/truenas/TrueNASStorageTopologyTable.tsx": "19e1e5b705285464607804f8a8d7b5174e39c856290bd8c6ce5dc5e1212ff2af" + }, + "routes": [ + "/truenas/storage", + "/truenas/storage?kind=disks" + ], + "viewports": [ + { + "width": 1440, + "height": 900 + }, + { + "width": 820, + "height": 700 + }, + { + "width": 390, + "height": 844 + } + ], + "states": [ + "Storage at 1440: every row is single-line again. Healthy rows leave Health empty, the archive pool reads Pool archive is DEGRADED: one member of archive mirror-0 is reporting checksum and SMART faults (truncated, +1, full list on hover), archive/cold reads Dataset is read-only, sdc reads Device /dev/sdc has SMART test failures, all in the Health column and coloured amber", + "Name cells hold only the name, so the reason no longer stacks under it", + "Disk scope at 1440: Endurance and Temp visible, sdc reason in Health", + "Tablet at 820 and dark mode at 1440: reasons truncate cleanly with the +1 kept", + "Phone at 390: the table is the contract phone projection (Resource, Kind, Usage, Disks) with status dots, Health hidden", + "Phone drawer: tapping archive shows DEGRADED and tapping sdc shows the SMART failure. The read-only dataset drawer only says Degraded, a pre-existing drawer gap left for a follow-up because the drawer model is also changed by the open services and VMs pull request", + "usePlatformWindowedItems is back to its main content, with rows uniform again" + ], + "interactions": [ + "Switched to the Physical disks scope", + "Tapped archive, archive/cold and sdc on a 390 phone and read each drawer" + ] +} diff --git a/frontend-modern/src/features/platformPage/__tests__/PlatformWindowedRows.test.tsx b/frontend-modern/src/features/platformPage/__tests__/PlatformWindowedRows.test.tsx index 109ffa7e9e..6aa10beb60 100644 --- a/frontend-modern/src/features/platformPage/__tests__/PlatformWindowedRows.test.tsx +++ b/frontend-modern/src/features/platformPage/__tests__/PlatformWindowedRows.test.tsx @@ -263,63 +263,4 @@ describe('PlatformWindowedRows', () => { } } }); - - it('averages the mounted table rows so a few taller rows do not size every unmounted row', () => { - // Exception rows can carry a reason line under the name and sort first. - // Taking the tallest of the leading rows as every row's height made the - // spacers overshoot and the window jump as rows scrolled out. - const items = () => Array.from({ length: 41 }, (_, index) => index); - const originalRect = Element.prototype.getBoundingClientRect; - const originalVisibility = (Element.prototype as Element & { checkVisibility?: () => boolean }) - .checkVisibility; - Element.prototype.getBoundingClientRect = function (this: Element) { - const raw = (this as HTMLElement).getAttribute?.('data-height'); - const height = raw ? Number(raw) : 0; - return { - height, - width: 0, - top: 0, - left: 0, - right: 0, - bottom: height, - x: 0, - y: 0, - toJSON: () => ({}), - } as DOMRect; - }; - (Element.prototype as Element & { checkVisibility?: () => boolean }).checkVisibility = () => - true; - - try { - const { container } = render(() => ( -