diff --git a/frontend-modern/scripts/canonical-platform-audit.mjs b/frontend-modern/scripts/canonical-platform-audit.mjs index 3498b6170..b9cf12cf9 100644 --- a/frontend-modern/scripts/canonical-platform-audit.mjs +++ b/frontend-modern/scripts/canonical-platform-audit.mjs @@ -1338,6 +1338,30 @@ const HELPER_RULES = [ message: 'Do not define local RAID state/device presentation helpers in component code. Use @/utils/raidPresentation instead.', }, + { + // Platform tables must align columns by kind, not by literal align string. + // The kind-based wrappers (getPlatformTableHeadClassForKind / + // getPlatformTableCellClassForKind) live in + // src/features/platformPage/sharedPlatformPage.tsx and resolve to a + // canonical alignment via src/features/platformPage/columnAlignment.ts. + // The legacy align-based helpers still exist so the wrappers can delegate + // to them, but no platform table should call them directly. If you hit + // this rule, switch the call site to the *ForKind variant and pick a + // column kind ('name' | 'text' | 'metric-bar' | 'numeric-value' | + // 'badge') for the column. + rule: 'canonical-platform-table/no-legacy-align-helper', + regex: /\bgetPlatformTable(?:Head|Cell)Class\(/g, + message: + 'Use getPlatformTable{Head,Cell}ClassForKind(kind) instead of the legacy align-based helper. See frontend-modern/src/features/platformPage/columnAlignment.ts for the canonical column kinds and their alignments.', + allowFiles: new Set([ + // The kind-based wrappers internally delegate to the align-based + // helpers; that's the one legitimate call site. + 'src/features/platformPage/sharedPlatformPage.tsx', + // KubernetesClustersTable is pending migration by another agent. + // Remove this entry once that work lands. + 'src/features/kubernetes/KubernetesClustersTable.tsx', + ]), + }, ]; const MAP_RULES = [ @@ -1723,7 +1747,8 @@ for (const dir of TARGET_DIRS) { const content = fs.readFileSync(filePath, 'utf8'); - for (const { rule, regex, message } of HELPER_RULES) { + for (const { rule, regex, message, allowFiles } of HELPER_RULES) { + if (allowFiles && allowFiles.has(relativePath)) continue; for (const match of content.matchAll(regex)) { pushMatch(relativePath, content, match.index ?? 0, rule, message); } 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 b3acb2370..dcb84643c 100644 --- a/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts +++ b/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts @@ -112,15 +112,22 @@ describe('platform overview layout guardrails', () => { }); it('keeps mobile host tables focused on useful operational columns', () => { - expect(dockerHostsTableSource).toContain('Host'); - expect(dockerHostsTableSource).toContain( - "CPU", + // Assertions use the canonical kind-based helpers + // (getPlatformTableHeadClassForKind('')) for files that have been + // migrated. KubernetesClustersTable still uses the legacy align-based + // helper because another agent has it mid-edit; the assertions below + // match that legacy form until its migration lands. + expect(dockerHostsTableSource).toMatch( + /getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Host/, ); - expect(dockerHostsTableSource).toContain( - "Memory", + expect(dockerHostsTableSource).toMatch( + /getPlatformTableHeadClassForKind\('metric-bar'\)[\s\S]{0,200}?CPU/, ); - expect(dockerHostsTableSource).toContain( - "Disk", + expect(dockerHostsTableSource).toMatch( + /getPlatformTableHeadClassForKind\('metric-bar'\)[\s\S]{0,200}?Memory/, + ); + expect(dockerHostsTableSource).toMatch( + /getPlatformTableHeadClassForKind\('metric-bar'\)[\s\S]{0,200}?Disk/, ); expect(kubernetesClustersTableSource).toContain( @@ -129,24 +136,24 @@ describe('platform overview layout guardrails', () => { expect(kubernetesClustersTableSource).toContain( "Nodes", ); - expect(kubernetesNodesTableSource).toContain( - 'Node', + expect(kubernetesNodesTableSource).toMatch( + /getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Node/, ); expect(kubernetesNodesTableSource).toContain( '{compactCapacityLabel()}', ); - expect(truenasSystemsTableSource).toContain( - 'System', + expect(truenasSystemsTableSource).toMatch( + /getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?System/, ); expect(truenasSystemsTableSource).toContain( '{formatPercent(storagePercent())}', ); - expect(vsphereHostsTableSource).toContain( - 'Host', + expect(vsphereHostsTableSource).toMatch( + /getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Host/, ); - expect(vsphereHostsTableSource).toContain( - "VMs", + expect(vsphereHostsTableSource).toMatch( + /getPlatformTableHeadClassForKind\('numeric-value'\)[\s\S]{0,200}?VMs/, ); expect(vsphereHostsTableSource).toContain('hidden md:table-cell'); });