From 77134632e2c34beff4a9a74541789fcd49f25834 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 12 Jun 2026 14:01:39 +0100 Subject: [PATCH] Register platform section tabs governance --- .../subsystems/frontend-primitives.md | 6 ++ .../scripts/shared-template-registry.json | 47 +++++++++++ .../SharedPrimitives.guardrails.test.ts | 80 +++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 34c44bf0e5..687f1efca9 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -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 diff --git a/frontend-modern/scripts/shared-template-registry.json b/frontend-modern/scripts/shared-template-registry.json index f1539ddbd6..2cd5cb1482 100644 --- a/frontend-modern/scripts/shared-template-registry.json +++ b/frontend-modern/scripts/shared-template-registry.json @@ -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", diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index 04edce3c07..d63fa93072 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -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<{