mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-22 11:45:52 +00:00
Register platform section tabs governance
This commit is contained in:
@@ -2404,6 +2404,12 @@ and Refresh action for platform page and table load failures; platform
|
||||
consumers own only the failure title, description, and refresh callback.
|
||||
Platform feature surfaces must not recreate an `EmptyState` plus local Refresh
|
||||
button for `Could not load...` states.
|
||||
Platform section tabs are registry-backed too. `PlatformSectionTabs` owns the
|
||||
workflow tab shell, hidden-single-tab behavior, active-link styling, link
|
||||
targeting, and active-page aria state for platform pages; platform page
|
||||
surfaces own only tab specs, the active tab choice, and aria-label copy.
|
||||
Platform feature surfaces must not rebuild local nav tab bars with
|
||||
`aria-current` and border-tab styling.
|
||||
Filter bars are registry-backed too. `FilterBar` owns resource-list filtering
|
||||
as a catalog of `FilterDef` entries, while `filterChipStatusDot` owns the
|
||||
small leading status-dot glyph used by filter options. Page and feature
|
||||
|
||||
@@ -164,6 +164,28 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-section-tabs",
|
||||
"category": "platform-section-tabs",
|
||||
"summary": "Platform page workflow tabs must compose PlatformSectionTabs instead of recreating page-local nav tab bars.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "PlatformSectionTabs"
|
||||
},
|
||||
"requiredConsumers": [
|
||||
{ "path": "src/features/docker/DockerPageSurface.tsx" },
|
||||
{ "path": "src/features/kubernetes/KubernetesPageSurface.tsx" },
|
||||
{ "path": "src/features/proxmox/ProxmoxPageSurface.tsx" },
|
||||
{ "path": "src/features/standalone/StandalonePageSurface.tsx" },
|
||||
{ "path": "src/features/truenas/TrueNASPageSurface.tsx" },
|
||||
{ "path": "src/features/vmware/VmwarePageSurface.tsx" }
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filter-bar-catalog-shell",
|
||||
"category": "filter-bar",
|
||||
@@ -292,6 +314,31 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "platform-section-tabs-local-nav",
|
||||
"category": "platform-section-tabs",
|
||||
"summary": "Platform page surfaces must not recreate the active-link tab navigation owned by PlatformSectionTabs.",
|
||||
"canonical": {
|
||||
"path": "src/features/platformPage/sharedPlatformPage.tsx",
|
||||
"export": "PlatformSectionTabs"
|
||||
},
|
||||
"scopes": [
|
||||
"src/features/docker",
|
||||
"src/features/kubernetes",
|
||||
"src/features/proxmox",
|
||||
"src/features/standalone",
|
||||
"src/features/truenas",
|
||||
"src/features/vmware"
|
||||
],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["aria-current={", "border-b-2", "border-b border-border"],
|
||||
"legacyReason": "Retired migration debt. Platform page section tabs must use PlatformSectionTabs; no page-local tab-bar exceptions are currently allowed.",
|
||||
"allowedPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filter-bar-consumer-legacy-page-controls",
|
||||
"category": "filter-bar",
|
||||
|
||||
@@ -1151,6 +1151,86 @@ describe('shared primitive guardrails', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps platform section navigation on the shared tabs template', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
rules?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
requiredConsumers?: Array<{ path?: string }>;
|
||||
}>;
|
||||
patternGuards?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
allPatterns?: string[];
|
||||
scopes?: string[];
|
||||
allowedPaths?: string[];
|
||||
}>;
|
||||
};
|
||||
const registeredRule = registry.rules?.find((rule) => rule.id === 'platform-section-tabs');
|
||||
const registeredGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'platform-section-tabs-local-nav',
|
||||
);
|
||||
const requiredConsumerPaths = [
|
||||
'src/features/docker/DockerPageSurface.tsx',
|
||||
'src/features/kubernetes/KubernetesPageSurface.tsx',
|
||||
'src/features/proxmox/ProxmoxPageSurface.tsx',
|
||||
'src/features/standalone/StandalonePageSurface.tsx',
|
||||
'src/features/truenas/TrueNASPageSurface.tsx',
|
||||
'src/features/vmware/VmwarePageSurface.tsx',
|
||||
];
|
||||
const platformPageSurfaceSources = [
|
||||
dockerPageSurfaceSource,
|
||||
kubernetesPageSurfaceSource,
|
||||
proxmoxPageSurfaceSource,
|
||||
standalonePageSurfaceSource,
|
||||
truenasPageSurfaceSource,
|
||||
vmwarePageSurfaceSource,
|
||||
];
|
||||
|
||||
expect(registeredRule?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(registeredRule?.canonical?.export).toBe('PlatformSectionTabs');
|
||||
expect(registeredRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual(
|
||||
expect.arrayContaining(requiredConsumerPaths),
|
||||
);
|
||||
expect(registeredGuard?.canonical?.path).toBe(
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
);
|
||||
expect(registeredGuard?.canonical?.export).toBe('PlatformSectionTabs');
|
||||
expect(registeredGuard?.allPatterns).toEqual([
|
||||
'aria-current={',
|
||||
'border-b-2',
|
||||
'border-b border-border',
|
||||
]);
|
||||
expect(registeredGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(registeredGuard?.scopes).toEqual(
|
||||
expect.arrayContaining([
|
||||
'src/features/docker',
|
||||
'src/features/kubernetes',
|
||||
'src/features/proxmox',
|
||||
'src/features/standalone',
|
||||
'src/features/truenas',
|
||||
'src/features/vmware',
|
||||
]),
|
||||
);
|
||||
|
||||
expect(sharedPlatformPageSource).toContain('export function PlatformSectionTabs');
|
||||
expect(sharedPlatformPageSource).toContain('props.tabs.length > 1');
|
||||
expect(sharedPlatformPageSource).toContain('href={tab.path}');
|
||||
expect(sharedPlatformPageSource).toContain('border-b-2');
|
||||
expect(sharedPlatformPageSource).toContain(
|
||||
"aria-current={props.active === tab.id ? 'page' : undefined}",
|
||||
);
|
||||
|
||||
for (const source of platformPageSurfaceSources) {
|
||||
expect(source).toContain('PlatformSectionTabs');
|
||||
expect(source).not.toContain('aria-current={');
|
||||
expect(source).not.toContain('border-b-2');
|
||||
expect(source).not.toContain('border-b border-border');
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps platform load-failure states on the shared error template', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
rules?: Array<{
|
||||
|
||||
Reference in New Issue
Block a user