diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index bd47fb5512..c866d04df2 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -2309,6 +2309,11 @@ not a replacement status card, CTA band, or page-local nested card. keeps the 30 percent `platform-table-name-column` anchor in that band. Tables sized by a weighted colgroup keep their own widths there, since column widths win over header widths. + A table whose columns need unequal room in that band (a long name beside + short counts) declares each header's percent through + `platformTableBandWidthStyle` or the sortable header's `bandWidth`, which + sets `--platform-table-band-width`; the shell applies it only inside the band, + after the name anchor, so md widths and phone widths are unchanged. Below a 360-pixel content width, the shared narrow layout promotes identity to 40 percent and may remove one additional lowest-priority context column via `platform-table-narrow-hidden` rather than shrinking names and metric values diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 26a302151d..b21e212e96 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -1177,6 +1177,19 @@ part that tells siblings apart (`checkout-api-6d…-x7k2p`, `prod…-03`), with full name on the title. Pod names, the Pods table's Node column and controller names use it. Deployment names are chosen by people, so they keep plain end truncation. +With one cluster in view, every Kubernetes scope cell repeated the cluster +name and crowded out the namespace that differs between rows, so the tables +show the namespace alone (`Cluster` for cluster-scoped objects) through +`kubernetesScopeDisplayLabel` and keep the full cluster/namespace scope on the +title; several clusters keep the combined label. The Deployments table keeps +Desired for wide tables only, because Ready already shows ready over desired, +and sizes the tablet band for a long name beside short counts. The +Configuration tables size their band and md widths for ConfigMap, Secret and +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. 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/3fa0808818f027a1.json b/frontend-modern/browser-verification/3fa0808818f027a1.json new file mode 100644 index 0000000000..7b7931d598 --- /dev/null +++ b/frontend-modern/browser-verification/3fa0808818f027a1.json @@ -0,0 +1,122 @@ +{ + "version": 1, + "base_sha": "3af37a88625701f6dad057fc8889d09a61dffd24", + "verified_at": "2026-10-05T11:01:05Z", + "result": "passed", + "changed_paths": [ + "frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesEventsTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesPodsTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesScopeText.tsx", + "frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx", + "frontend-modern/src/features/kubernetes/kubernetesPageModel.ts", + "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx", + "frontend-modern/src/index.css" + ], + "content_sha256": { + "frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx": "b66c8c5c705b9478d98ba2bfd51ff199c07619ecf40516d7f276a13c5409a825", + "frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx": "bc3e01de166d4467e7b547a72958091c465768ef552ae31118969d6b4d6c36fd", + "frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx": "b1b184557d03331d8d2ef19d9ab8af9d083013ac3b839fde719bd5d5e4d12d9e", + "frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx": "95cca80263abaf11c0025f1c0bf7a2ccc4421acb9a0d2c611096602353ea75d5", + "frontend-modern/src/features/kubernetes/KubernetesEventsTable.tsx": "814ce1c8358cdde955da9707498d491bc3081dded52e86405edd9517e3f71f65", + "frontend-modern/src/features/kubernetes/KubernetesNetworkingTable.tsx": "6a0c1e57cddf59acaf0f0a21690fde5db75f08a9fddd4ac957086b5d7ac307c9", + "frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx": "487541b013886c3f371762a585443ae790d6ec7c72531ac7991ec1a5c76b73a5", + "frontend-modern/src/features/kubernetes/KubernetesPodsTable.tsx": "5ed2a52f1a7e5c27a96d2df832d4b80ef210873c84051eb396d4f9cf31f42f51", + "frontend-modern/src/features/kubernetes/KubernetesPolicyTable.tsx": "91293a32d6cb195a07d39f472c9a7e204bd96de709c65d82728e30d74aac77a9", + "frontend-modern/src/features/kubernetes/KubernetesScopeText.tsx": "50a0468caf73eb7658bbc90e26cd5b48702af7a441e384ab70fde5a6a0daf557", + "frontend-modern/src/features/kubernetes/KubernetesServicesTable.tsx": "48ccb976014015513b1aa52391da969604c541d2f27a20d8efac098d24890f15", + "frontend-modern/src/features/kubernetes/KubernetesStorageTable.tsx": "b0c87e0386b0d4f7440a7859a106d73daf611867bfd88418e1c6ed7cf8456ae9", + "frontend-modern/src/features/kubernetes/kubernetesPageModel.ts": "9cf09fca38e4c64616e1cd4ee578c6c03bddb810498abdd974e0450a68aa81b1", + "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx": "843a1efe473f4092a1ee508bd1634bc51fdcce395e2fe7dd41998ba1c7aebf8b", + "frontend-modern/src/index.css": "ce8f3f0d5a8da290fad1545eedca6a46d4211b4e98239fb1a8e4773cf063e4b2" + }, + "routes": [ + "/kubernetes/overview", + "/kubernetes/nodes", + "/kubernetes/workloads", + "/kubernetes/services", + "/kubernetes/storage", + "/kubernetes/configuration", + "/kubernetes/events" + ], + "viewports": [ + { + "width": 360, + "height": 900 + }, + { + "width": 390, + "height": 1100 + }, + { + "width": 430, + "height": 900 + }, + { + "width": 520, + "height": 900 + }, + { + "width": 600, + "height": 1000 + }, + { + "width": 650, + "height": 1000 + }, + { + "width": 700, + "height": 1000 + }, + { + "width": 767, + "height": 1000 + }, + { + "width": 768, + "height": 1000 + }, + { + "width": 820, + "height": 1000 + }, + { + "width": 900, + "height": 1000 + }, + { + "width": 1100, + "height": 1000 + }, + { + "width": 1300, + "height": 1000 + }, + { + "width": 1440, + "height": 1000 + } + ], + "states": [ + "Clip sweep (scrollWidth over clientWidth on every cell, every Kubernetes tab, 600 to 1440 wide, screen-reader text excluded): clipped cells fall from 566 to 356 against main with no column worse at any width; Deployment, Resource and Namespace clips on Workloads and Configuration go from 25 to 0, Scope from 136 to 7, Age from 22 to 1", + "Workloads at 700: every deployment and namespace reads in full (main showed ingress-nginx-cont... and monitori...); columns are Deployment, Namespace, Updated, Ready, Available, Age with Ready reading 2/2; at 1300 and 1440 Cluster and Desired return and Ready reads 2", + "Configuration at 700: ConfigMap, Secret and RBAC names read in full (main cut them to checkout-api-runti... and payments-worker-...), Scope shows the namespace (apps, monitoring, services, Cluster) instead of Production EU/ser, and long Lifecycle and Data shape values end in an ellipsis inside their own cell where main spilled 1 subject - Service past the card edge", + "Row text alignment on every Kubernetes tab at 700 and 1440: each row's cell text sits on one line (0px spread); main's Scope and Data shape spans sat about 4px above the row", + "All 205 block truncate summary spans are the only content of their cell and sit in fixed-layout tables", + "Phone at 390: Deployments Age reads 18h ago (main showed 18h a...), Ready keeps 2/2; Configuration shows the namespace scope and ellipsizes Data inside the cell; at 360 Age stays hidden as before", + "Scope cell exposes the full cluster/namespace scope to assistive technology through a screen-reader span and keeps it on the title" + ], + "interactions": [ + "Ran the clip sweep on main (reverse-applied patch) and on the change across seven widths", + "Tuned tablet-band widths live by injecting --platform-table-band-width at 600, 650, 700 and 767", + "Measured row text alignment and block-span containment in the live DOM", + "Captured before/after table screenshots at 700 and 390 for Workloads and Configuration" + ] +} diff --git a/frontend-modern/src/__tests__/App.architecture.test.ts b/frontend-modern/src/__tests__/App.architecture.test.ts index a56a8d8ddc..e9d9cc865b 100644 --- a/frontend-modern/src/__tests__/App.architecture.test.ts +++ b/frontend-modern/src/__tests__/App.architecture.test.ts @@ -274,6 +274,20 @@ describe('App architecture', () => { expect(appStylesSource).not.toContain('white-space: normal;\n -webkit-box-orient'); }); + it('keeps the desired replica count on screen in the Deployments table', () => { + // Desired returns with the 72rem tier, so Ready's own /desired total hides + // only there, after its base rule so the container rule wins. + const base = appStylesSource.indexOf('.k8s-deployment-ready-total {\n display: inline;'); + const hide = appStylesSource.indexOf( + '@container (min-width: 72rem) {\n .k8s-deployment-ready-total {\n display: none;', + ); + expect(base).toBeGreaterThan(-1); + expect(hide).toBeGreaterThan(base); + expect(appStylesSource.match(/\.k8s-deployment-ready-total \{\s*display: none;/g)).toHaveLength( + 1, + ); + }); + it('keeps the identity anchor between the phone projection and the md column widths', () => { // Column visibility is container-led but md:w-[...] widths are viewport // media queries, so a 544-767px band showed every column with no widths and @@ -283,6 +297,11 @@ describe('App architecture', () => { expect(band).toMatch( /^@media \(width < 768px\) \{\s*@container \(min-width: 34rem\) \{\s*\.table-scroll-shell > \.table-fixed\.platform-table th\.platform-table-name-column \{\s*width: 30%;/, ); + // A table that needs unequal room in the band declares each header's + // width, and the shell applies it only there (after the name anchor). + expect(band).toMatch( + /width: 30%;\s*\}[\s\S]*?th\[style\*='--platform-table-band-width'\] \{\s*width: var\(--platform-table-band-width\);/, + ); }); it('keeps compact phone settings spacing scoped to the settings content shell', () => { diff --git a/frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx b/frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx index 9f92539052..5713ea2d46 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesAutoscalingTable.tsx @@ -32,7 +32,9 @@ import { filterKubernetesResources, kubernetesScopeLabel, type KubernetesResourceStatusFilter, + isSingleKubernetesCluster, } from './kubernetesPageModel'; +import { KubernetesScopeText } from './KubernetesScopeText'; const autoscalerName = (resource: Resource): string => asTrimmedString(resource.displayName) || asTrimmedString(resource.name) || resource.id; @@ -105,6 +107,7 @@ export const KubernetesAutoscalingTable: Component<{ externalSearch?: () => string; externalStatus?: () => KubernetesResourceStatusFilter; }> = (props) => { + const singleCluster = createMemo(() => isSingleKubernetesCluster(props.resources)); const tableState = createPlatformTableFilterState({ resources: () => props.resources, initialStatus: 'all' as KubernetesResourceStatusFilter, @@ -237,7 +240,6 @@ export const KubernetesAutoscalingTable: Component<{ {(resource) => { const indicator = () => getSimpleStatusIndicator(resource.status); const name = () => autoscalerName(resource); - const scope = () => kubernetesScopeLabel(resource); const target = () => targetRef(resource); const metrics = () => metricSources(resource); const labels = () => labelSummary(resource); @@ -273,14 +275,16 @@ export const KubernetesAutoscalingTable: Component<{ - + {target()} @@ -306,14 +310,14 @@ export const KubernetesAutoscalingTable: Component<{ diff --git a/frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx b/frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx index c1424aff0f..a4cea17020 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesConfigTable.tsx @@ -31,7 +31,9 @@ import { filterKubernetesResources, kubernetesScopeLabel, type KubernetesResourceStatusFilter, + isSingleKubernetesCluster, } from './kubernetesPageModel'; +import { KubernetesScopeText } from './KubernetesScopeText'; // ConfigMaps and Secrets are intentionally rendered as API metadata. Pulse may // know that keys exist, but metadata-only collection must never imply that @@ -198,6 +200,7 @@ export const KubernetesConfigTable: Component<{ externalSearch?: () => string; externalStatus?: () => KubernetesResourceStatusFilter; }> = (props) => { + const singleCluster = createMemo(() => isSingleKubernetesCluster(props.resources)); const tableState = createPlatformTableFilterState({ resources: () => props.resources, initialStatus: 'all' as KubernetesResourceStatusFilter, @@ -264,7 +267,8 @@ export const KubernetesConfigTable: Component<{ kind="name" sort={sort} sortKey="resource" - class="platform-table-mobile-w-30 md:w-[22%]" + class="platform-table-mobile-w-30 md:w-[27%]" + bandWidth={35} > Resource @@ -272,7 +276,8 @@ export const KubernetesConfigTable: Component<{ kind="text" sort={sort} sortKey="kind" - class="platform-table-mobile-w-15 md:w-[13%]" + class="platform-table-mobile-w-15 md:w-[15%]" + bandWidth={16} > Kind @@ -280,7 +285,8 @@ export const KubernetesConfigTable: Component<{ kind="text" sort={sort} sortKey="scope" - class="platform-table-mobile-w-20 md:w-[16%]" + class="platform-table-mobile-w-20 md:w-[10%]" + bandWidth={14} > Scope @@ -288,7 +294,8 @@ export const KubernetesConfigTable: Component<{ kind="text" sort={sort} sortKey="lifecycle" - class="platform-table-phone-hidden md:w-[16%]" + class="platform-table-phone-hidden md:w-[17%]" + bandWidth={16} > @@ -296,6 +303,7 @@ export const KubernetesConfigTable: Component<{ kind="text" sort={sort} class="platform-table-mobile-w-15 md:w-[14%]" + bandWidth={19} > @@ -322,7 +330,6 @@ export const KubernetesConfigTable: Component<{ const indicator = () => getSimpleStatusIndicator(resource.status); const name = () => configName(resource); const kind = () => configKind(resource); - const scope = () => kubernetesScopeLabel(resource); const state = () => lifecycleOrTrust(resource); const data = () => dataShape(resource); const refs = () => serviceAccountRefs(resource); @@ -364,9 +371,11 @@ export const KubernetesConfigTable: Component<{ - - {scope()} - + - + {data().label} diff --git a/frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx b/frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx index 00d3c6da90..a70172c5b5 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesControllersTable.tsx @@ -36,7 +36,9 @@ import { kubernetesScopeLabel, mapKubernetesControllerStatus, type KubernetesResourceStatusFilter, + isSingleKubernetesCluster, } from './kubernetesPageModel'; +import { KubernetesScopeText } from './KubernetesScopeText'; import { KubernetesNameText } from './KubernetesNameText'; const controllerName = (resource: Resource): string => @@ -243,6 +245,7 @@ export const KubernetesControllersTable: Component<{ externalSearch?: () => string; externalStatus?: () => KubernetesResourceStatusFilter; }> = (props) => { + const singleCluster = createMemo(() => isSingleKubernetesCluster(props.resources)); const tableState = createPlatformTableFilterState({ resources: () => props.resources, initialStatus: 'all' as KubernetesResourceStatusFilter, @@ -397,7 +400,6 @@ export const KubernetesControllersTable: Component<{ const indicator = () => mapKubernetesControllerStatus(resource); const name = () => controllerName(resource); const kind = () => controllerKind(resource); - const scope = () => kubernetesScopeLabel(resource); const target = () => targetValue(resource); const exceptions = () => exceptionSummary(resource); const detail = () => apiDetail(resource); @@ -439,14 +441,16 @@ export const KubernetesControllersTable: Component<{ - + {target()} @@ -468,7 +472,7 @@ export const KubernetesControllersTable: Component<{ - + {exceptions()} diff --git a/frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx b/frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx index 5407855cac..44baa92736 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx @@ -174,13 +174,18 @@ export const KubernetesDeploymentsTable: Component<{ The integer-count columns (Desired / Updated / Ready / Available) trim to what their headers plus 1-2 digit values need. The basic tier restores namespace context - without crowding the phone tier. + without crowding the phone tier. Desired is the + denominator Ready already shows (2/2), so it waits for a + wide table. bandWidth sizes the tablet band below the + md widths, where a long deployment name needs about a + third of the row and the counts need little. */} Deployment @@ -188,7 +193,8 @@ export const KubernetesDeploymentsTable: Component<{ kind="text" sort={sort} sortKey="namespace" - class="platform-table-mobile-w-15 md:w-[20%]" + class="platform-table-mobile-w-15 md:w-[18%]" + bandWidth={16} > Namespace @@ -204,7 +210,7 @@ export const KubernetesDeploymentsTable: Component<{ kind="numeric-value" sort={sort} sortKey="desired" - class="platform-table-phone-hidden md:w-[8%]" + class="hidden md:table-cell md:w-[8%]" > @@ -213,6 +219,7 @@ export const KubernetesDeploymentsTable: Component<{ sort={sort} sortKey="updated" class="platform-table-phone-hidden md:w-[8%]" + bandWidth={12} > @@ -220,7 +227,8 @@ export const KubernetesDeploymentsTable: Component<{ kind="numeric-value" sort={sort} sortKey="ready" - class="platform-table-mobile-w-15 md:w-[7%]" + class="platform-table-mobile-w-10 md:w-[7%]" + bandWidth={9.5} > @@ -229,6 +237,7 @@ export const KubernetesDeploymentsTable: Component<{ sort={sort} sortKey="available" class="platform-table-phone-hidden md:w-[9%]" + bandWidth={14} > @@ -236,7 +245,8 @@ export const KubernetesDeploymentsTable: Component<{ kind="numeric-value" sort={sort} sortKey="age" - class="platform-table-mobile-w-10 md:w-[6%] platform-table-narrow-hidden" + class="platform-table-mobile-w-15 md:w-[8%] platform-table-narrow-hidden" + bandWidth={12.5} > Age @@ -292,7 +302,7 @@ export const KubernetesDeploymentsTable: Component<{ {cluster()}