mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Enforce canonical platform table alignment in pre-push audit
Adds canonical-platform-table/no-legacy-align-helper to
canonical-platform-audit.mjs: any platform table file that calls the
legacy `getPlatformTableHeadClass(<align>)` or
`getPlatformTableCellClass(<align>)` helper now fails the audit (which
runs in the pre-push hook). Authors are pointed at the kind-based
wrappers and the canonical column-kind file.
Two intentional allowlist entries:
- sharedPlatformPage.tsx: the kind-based wrappers internally delegate
to the align-based helpers; that's the one legitimate call site.
- KubernetesClustersTable.tsx: another agent has this file
mid-staged in their working tree. Removing this entry is part of
that file's migration commit (drop the entry whenever the file
moves to the *ForKind wrappers).
To support per-rule exemptions cleanly, the audit's inner loop now
honours an optional `allowFiles: Set<string>` on each HELPER_RULE
entry. Existing rules are unchanged; only the new rule uses it.
The platformOverviewLayout.guardrails.test.ts assertions are updated
to match the kind-based forms (regex match on the new
getPlatformTableHeadClassForKind('<kind>') ... <label> shape) for the
migrated files. The two Kubernetes Clusters assertions continue to
match the legacy form until that file migrates.
Workspace-level CLAUDE.md and AGENTS.md (at /Volumes/Development/pulse/,
outside this git repo) also got a one-paragraph entry pointing at the
canonical source-of-truth file and the audit rule, so every agent
session that reads those bootstrap files at startup picks up the
convention.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
+22
-15
@@ -112,15 +112,22 @@ describe('platform overview layout guardrails', () => {
|
||||
});
|
||||
|
||||
it('keeps mobile host tables focused on useful operational columns', () => {
|
||||
expect(dockerHostsTableSource).toContain('<TableHead class={getPlatformTableHeadClass()}>Host');
|
||||
expect(dockerHostsTableSource).toContain(
|
||||
"<TableHead class={getPlatformTableHeadClass('right')}>CPU",
|
||||
// Assertions use the canonical kind-based helpers
|
||||
// (getPlatformTableHeadClassForKind('<kind>')) 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(
|
||||
"<TableHead class={getPlatformTableHeadClass('right')}>Memory",
|
||||
expect(dockerHostsTableSource).toMatch(
|
||||
/getPlatformTableHeadClassForKind\('metric-bar'\)[\s\S]{0,200}?CPU/,
|
||||
);
|
||||
expect(dockerHostsTableSource).toContain(
|
||||
"<TableHead class={getPlatformTableHeadClass('right')}>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(
|
||||
"<TableHead class={getPlatformTableHeadClass('right')}>Nodes",
|
||||
);
|
||||
expect(kubernetesNodesTableSource).toContain(
|
||||
'<TableHead class={getPlatformTableHeadClass()}>Node',
|
||||
expect(kubernetesNodesTableSource).toMatch(
|
||||
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Node/,
|
||||
);
|
||||
expect(kubernetesNodesTableSource).toContain(
|
||||
'<span class="md:hidden">{compactCapacityLabel()}</span>',
|
||||
);
|
||||
|
||||
expect(truenasSystemsTableSource).toContain(
|
||||
'<TableHead class={getPlatformTableHeadClass()}>System',
|
||||
expect(truenasSystemsTableSource).toMatch(
|
||||
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?System/,
|
||||
);
|
||||
expect(truenasSystemsTableSource).toContain(
|
||||
'<span class="md:hidden">{formatPercent(storagePercent())}</span>',
|
||||
);
|
||||
expect(vsphereHostsTableSource).toContain(
|
||||
'<TableHead class={getPlatformTableHeadClass()}>Host',
|
||||
expect(vsphereHostsTableSource).toMatch(
|
||||
/getPlatformTableHeadClassForKind\('name'\)[\s\S]{0,200}?Host/,
|
||||
);
|
||||
expect(vsphereHostsTableSource).toContain(
|
||||
"<TableHead class={getPlatformTableHeadClass('right')}>VMs",
|
||||
expect(vsphereHostsTableSource).toMatch(
|
||||
/getPlatformTableHeadClassForKind\('numeric-value'\)[\s\S]{0,200}?VMs/,
|
||||
);
|
||||
expect(vsphereHostsTableSource).toContain('hidden md:table-cell');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user