Converge platform table width calculation

This commit is contained in:
rcourtman
2026-06-13 14:26:58 +01:00
parent 6c60146372
commit 1aec698bc7
8 changed files with 193 additions and 16 deletions
@@ -2645,6 +2645,13 @@ empty-cell markers, and tabular-number styling for dense platform table cells.
Proxmox replication last-duration cells and Standalone availability-check poll
interval cells must compose that primitive instead of declaring local
seconds/minutes helpers.
Responsive platform table width normalization is registry-backed too.
`getPlatformTableWeightedColumnWidthStyle` owns visible-column weight
normalization, zero-width fallback, and stable four-decimal percentage
formatting for dense platform table models. Docker / Podman container columns
and Proxmox host columns must keep their domain column IDs, layout breakpoints,
and weight maps local, but must call that shared helper instead of declaring
local `formatPercentage` / `toFixed(4)` width helpers.
Platform table numeric fallback rendering is registry-backed too.
`PlatformTableNumberValue` owns finite-number checking, tabular-number styling,
custom empty-marker support, and caller-owned number formatting for dense
@@ -278,6 +278,12 @@ That boundary covers Proxmox replication last-sync duration cells and
Standalone availability-check poll interval cells, and future duration-like
platform table values must join the same shared primitive registry rule before
adding table-local formatting.
Responsive table layout keeps the same split: unified-resource and
source-specific table models own which columns are visible at each breakpoint
and the relative weights for those columns, while dense platform table
rendering must use `getPlatformTableWeightedColumnWidthStyle` for
visible-column percentage normalization, zero-width fallback, and stable
percentage formatting instead of declaring local width-percent helpers.
Optional numeric table cells follow the same split: unified-resource consumers
own which count or replica field is meaningful, whether the domain should
zero-default an absent scheduler/service/inventory count, whether a
@@ -1559,6 +1559,34 @@
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-weighted-column-width-style",
"category": "platform-table-layout",
"summary": "Responsive platform table models must use the shared weighted column-width helper for visible-column percentage normalization instead of declaring local percentage-format helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "getPlatformTableWeightedColumnWidthStyle"
},
"requiredConsumers": [
{ "path": "src/features/docker/dockerContainerTableModel.ts" },
{ "path": "src/features/proxmox/proxmoxHostTableModel.ts" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/dockerContainerTableModel.ts",
"patterns": ["const formatPercentage", "value.toFixed(4)"]
},
{
"path": "src/features/proxmox/proxmoxHostTableModel.ts",
"patterns": ["const formatPercentage", "value.toFixed(4)"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-integer-value-format",
"category": "platform-table-text-value",
@@ -2793,6 +2821,33 @@
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-local-weighted-width-helper",
"category": "platform-table-layout",
"summary": "Responsive platform table models must not recreate local weighted-width percentage helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "getPlatformTableWeightedColumnWidthStyle"
},
"scopes": [
"src/features/docker/dockerContainerTableModel.ts",
"src/features/proxmox/proxmoxHostTableModel.ts"
],
"extensions": [".ts"],
"allPatterns": [
"const formatPercentage",
"value.toFixed(4)",
"return { width: formatPercentage(width) }"
],
"legacyReason": "Retired migration debt. Responsive platform table width normalization must use getPlatformTableWeightedColumnWidthStyle so percentage rounding and zero-width fallback stay shared.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-local-truenas-count-cell-class",
"category": "platform-table-text-value",
@@ -149,6 +149,7 @@ import workloadsTableSource from '@/components/Workloads/WorkloadsTable.tsx?raw'
import workloadPanelSource from '@/components/Workloads/WorkloadPanel.tsx?raw';
import guestRowStateSource from '@/components/Workloads/useGuestRowState.ts?raw';
import workloadSelectionStateSource from '@/components/Workloads/useWorkloadSelectionState.ts?raw';
import dockerContainerTableModelSource from '@/features/docker/dockerContainerTableModel.ts?raw';
import dockerContainersTableSource from '@/features/docker/DockerContainersTable.tsx?raw';
import dockerHostsTableSource from '@/features/docker/DockerHostsTable.tsx?raw';
import dockerNativeTableSharedSource from '@/features/docker/DockerNativeTableShared.tsx?raw';
@@ -4323,6 +4324,82 @@ describe('shared primitive guardrails', () => {
}
});
it('keeps responsive platform table column widths on the shared weighted helper', () => {
const registry = JSON.parse(sharedTemplateRegistrySource) as {
rules?: Array<{
id: string;
canonical?: { path?: string; export?: string };
requiredConsumers?: Array<{ path?: string }>;
forbiddenPatterns?: Array<{ path?: string; patterns?: string[] }>;
}>;
patternGuards?: Array<{
id: string;
canonical?: { path?: string; export?: string };
allPatterns?: string[];
scopes?: string[];
allowedPaths?: string[];
ignoredPaths?: string[];
}>;
};
const registeredRule = registry.rules?.find(
(rule) => rule.id === 'platform-table-weighted-column-width-style',
);
const localHelperGuard = registry.patternGuards?.find(
(guard) => guard.id === 'platform-table-local-weighted-width-helper',
);
const platformWidthConsumers: Array<[string, string]> = [
['src/features/docker/dockerContainerTableModel.ts', dockerContainerTableModelSource],
['src/features/proxmox/proxmoxHostTableModel.ts', proxmoxHostTableModelSource],
];
const platformWidthConsumerPaths = platformWidthConsumers.map(([path]) => path);
expect(registeredRule?.canonical?.path).toBe(
'src/features/platformPage/sharedPlatformPage.tsx',
);
expect(registeredRule?.canonical?.export).toBe('getPlatformTableWeightedColumnWidthStyle');
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual(
platformWidthConsumerPaths,
);
expect(registeredRule?.forbiddenPatterns).toEqual([
{
path: 'src/features/docker/dockerContainerTableModel.ts',
patterns: ['const formatPercentage', 'value.toFixed(4)'],
},
{
path: 'src/features/proxmox/proxmoxHostTableModel.ts',
patterns: ['const formatPercentage', 'value.toFixed(4)'],
},
]);
expect(localHelperGuard?.canonical?.path).toBe(
'src/features/platformPage/sharedPlatformPage.tsx',
);
expect(localHelperGuard?.canonical?.export).toBe('getPlatformTableWeightedColumnWidthStyle');
expect(localHelperGuard?.allPatterns).toEqual([
'const formatPercentage',
'value.toFixed(4)',
'return { width: formatPercentage(width) }',
]);
expect(localHelperGuard?.scopes).toEqual([
'src/features/docker/dockerContainerTableModel.ts',
'src/features/proxmox/proxmoxHostTableModel.ts',
]);
expect(localHelperGuard?.allowedPaths ?? []).toHaveLength(0);
expect(localHelperGuard?.ignoredPaths ?? []).toHaveLength(0);
expect(sharedPlatformPageSource).toContain(
'export const getPlatformTableWeightedColumnWidthStyle',
);
for (const [path, source] of platformWidthConsumers) {
expect(source).toContain('getPlatformTableWeightedColumnWidthStyle');
const forbiddenPatterns =
registeredRule?.forbiddenPatterns?.find((entry) => entry.path === path)?.patterns ?? [];
for (const pattern of forbiddenPatterns) {
expect(source).not.toContain(pattern);
}
}
});
it('keeps platform table number-value fallbacks on the shared primitive', () => {
const registry = JSON.parse(sharedTemplateRegistrySource) as {
rules?: Array<{
@@ -2,6 +2,7 @@ import type { JSX } from 'solid-js';
import type { WorkloadTableLayoutMode } from '@/components/Workloads/guestRowModel';
import type { PlatformTableColumnKind } from '@/features/platformPage/columnAlignment';
import { getPlatformTableWeightedColumnWidthStyle } from '@/features/platformPage/sharedPlatformPage';
export type DockerContainerTableColumnId =
| 'container'
@@ -119,8 +120,6 @@ const DOCKER_CONTAINER_RESPONSIVE_WIDTHS: Record<
},
};
const formatPercentage = (value: number): string => `${Number(value.toFixed(4))}%`;
export const getDockerContainerVisibleColumnsForLayout = (
layoutMode: WorkloadTableLayoutMode,
includeRuntime: boolean,
@@ -148,11 +147,7 @@ export const getDockerContainerColumnWidthStyle = (
layoutMode === 'wide'
? DOCKER_CONTAINER_DESKTOP_WIDTHS
: DOCKER_CONTAINER_RESPONSIVE_WIDTHS[layoutMode];
const columnWeight = weights[columnId] ?? 0;
const totalWeight = visibleColumnIds.reduce((total, id) => total + (weights[id] ?? 0), 0);
const width = totalWeight > 0 ? (columnWeight / totalWeight) * 100 : 0;
return { width: formatPercentage(width) };
return getPlatformTableWeightedColumnWidthStyle(columnId, weights, visibleColumnIds);
};
// The Docker container table already has a row detail drawer for forensic
@@ -25,6 +25,7 @@ import {
filterPlatformResources,
formatPlatformTableTextValue,
getPlatformTableFiniteMetric,
getPlatformTableWeightedColumnWidthStyle,
summarizePlatformTableValues,
type PlatformResourceStatusFilter,
} from '../sharedPlatformPage';
@@ -419,6 +420,33 @@ describe('PlatformTableDurationValue', () => {
});
});
describe('getPlatformTableWeightedColumnWidthStyle', () => {
it('normalizes visible weighted table columns into stable width percentages', () => {
const weights = {
container: 32,
state: 14,
cpu: 18,
memory: 22,
updates: 14,
actions: 14,
};
const visibleColumnIds = ['container', 'state', 'cpu', 'memory', 'updates', 'actions'] as const;
expect(
getPlatformTableWeightedColumnWidthStyle('container', weights, visibleColumnIds),
).toEqual({
width: '28.0702%',
});
expect(getPlatformTableWeightedColumnWidthStyle('memory', weights, visibleColumnIds)).toEqual({
width: '19.2982%',
});
});
it('returns a zero width when no visible weighted columns are present', () => {
expect(getPlatformTableWeightedColumnWidthStyle('node', {}, [])).toEqual({ width: '0%' });
});
});
describe('formatPlatformTableIntegerValue', () => {
it('formats rounded integer labels with canonical empty markers', () => {
const expected = new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(1235);
@@ -358,6 +358,21 @@ export function PlatformTableDurationValue(props: {
);
}
const formatPlatformTableWidthPercentage = (value: number): string =>
`${Number(value.toFixed(4))}%`;
export const getPlatformTableWeightedColumnWidthStyle = <ColumnId extends string>(
columnId: ColumnId,
weights: Partial<Record<ColumnId, number>>,
visibleColumnIds: readonly ColumnId[],
): JSX.CSSProperties => {
const columnWeight = weights[columnId] ?? 0;
const totalWeight = visibleColumnIds.reduce((total, id) => total + (weights[id] ?? 0), 0);
const width = totalWeight > 0 ? (columnWeight / totalWeight) * 100 : 0;
return { width: formatPlatformTableWidthPercentage(width) };
};
const platformTableIntegerFormatter = new Intl.NumberFormat(undefined, {
maximumFractionDigits: 0,
});
@@ -2,6 +2,7 @@ import type { JSX } from 'solid-js';
import type { WorkloadTableLayoutMode } from '@/components/Workloads/guestRowModel';
import type { PlatformTableColumnKind } from '@/features/platformPage/columnAlignment';
import { getPlatformTableWeightedColumnWidthStyle } from '@/features/platformPage/sharedPlatformPage';
export type ProxmoxHostTableColumnId =
| 'node'
@@ -82,8 +83,6 @@ const HOST_COLUMN_RESPONSIVE_WEIGHTS: Record<
compact: HOST_COLUMN_DESKTOP_WIDTHS,
};
const formatPercentage = (value: number): string => `${Number(value.toFixed(4))}%`;
// Column order follows the canonical recommended ordering documented in
// columnAlignment.ts: identity → context → bars (CPU/Memory/Disk
// contiguous) → diagnostic (Temp) → time (Uptime) → inventory counts
@@ -119,11 +118,7 @@ export const getProxmoxHostColumnWidthStyle = (
): JSX.CSSProperties => {
const weights =
layoutMode === 'wide' ? HOST_COLUMN_DESKTOP_WIDTHS : HOST_COLUMN_RESPONSIVE_WEIGHTS[layoutMode];
const columnWeight = weights[columnId] ?? 0;
const totalWeight = visibleColumnIds.reduce((total, id) => total + (weights[id] ?? 0), 0);
const width = totalWeight > 0 ? (columnWeight / totalWeight) * 100 : 0;
return { width: formatPercentage(width) };
return getPlatformTableWeightedColumnWidthStyle(columnId, weights, visibleColumnIds);
};
// Only the `wide` layout (>= 1440px viewport) reserves a fixed 1240px floor so
@@ -134,5 +129,4 @@ export const getProxmoxHostColumnWidthStyle = (
// fits the container exactly and the bars scale down gracefully instead.
export const getProxmoxHostTableMinWidthClass = (
layoutMode: WorkloadTableLayoutMode,
): 'min-w-full' | 'min-w-[1240px]' =>
layoutMode === 'wide' ? 'min-w-[1240px]' : 'min-w-full';
): 'min-w-full' | 'min-w-[1240px]' => (layoutMode === 'wide' ? 'min-w-[1240px]' : 'min-w-full');