Files
pulse/frontend-modern/scripts/shared-template-registry.json
T
2026-06-13 11:32:50 +01:00

3814 lines
170 KiB
JSON

{
"version": 1,
"rules": [
{
"id": "runtime-web-interface-name-link",
"category": "resource-launch",
"summary": "Runtime resource rows must launch saved or inferred web-interface URLs through the shared name-link template, not a page-local link or separate web column.",
"canonical": {
"path": "src/components/shared/WebInterfaceNameLink.tsx",
"export": "WebInterfaceNameLink"
},
"requiredConsumers": [
{
"path": "src/components/shared/NodeGroupHeader.tsx"
},
{
"path": "src/components/Workloads/GuestRow.tsx"
},
{
"path": "src/features/standalone/AgentsMachinesTable.tsx"
},
{
"path": "src/features/proxmox/ProxmoxNodesTable.tsx"
}
],
"forbiddenPatterns": [
{
"path": "src/components/Workloads/GuestRow.tsx",
"patterns": ["target=\"_blank\"", "rel=\"noopener noreferrer\""]
},
{
"path": "src/components/shared/NodeGroupHeader.tsx",
"patterns": ["target=\"_blank\"", "rel=\"noopener noreferrer\""]
},
{
"path": "src/features/standalone/AgentsMachinesTable.tsx",
"patterns": [
"target=\"_blank\"",
"rel=\"noopener noreferrer\"",
"AgentMachineWebLinkCell",
"data-agent-machine-web-link"
]
},
{
"path": "src/features/standalone/agentMachineTableModel.ts",
"patterns": ["id: 'web'", "label: 'Web'"]
},
{
"path": "src/features/proxmox/ProxmoxNodesTable.tsx",
"patterns": [
"target=\"_blank\"",
"rel=\"noopener noreferrer\"",
"data-proxmox-host-web-link"
]
},
{
"path": "src/features/proxmox/proxmoxHostTableModel.ts",
"patterns": ["id: 'web'", "label: 'Web'"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/NodeGroupHeader.test.tsx",
"src/features/standalone/__tests__/AgentsMachinesTable.test.tsx",
"src/components/Workloads/__tests__/GuestRow.test.tsx",
"src/features/proxmox/__tests__/ProxmoxNodesTable.test.tsx",
"src/features/proxmox/__tests__/proxmoxHostTableModel.test.ts"
]
},
{
"id": "inline-detail-table-row-shell",
"category": "row-detail",
"summary": "Platform, workload, and infrastructure inline detail rows must compose the shared row/cell/content shell instead of recreating page-local surface-alt drawer rows.",
"canonical": {
"path": "src/components/shared/InlineDetailTableRow.tsx",
"export": "InlineDetailTableRow"
},
"requiredConsumers": [
{ "path": "src/components/Infrastructure/UnifiedResourceHostTableCard.tsx" },
{ "path": "src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx" },
{ "path": "src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx" },
{ "path": "src/components/Workloads/WorkloadPanel.tsx" },
{ "path": "src/features/docker/DockerAlertsTable.tsx" },
{ "path": "src/features/docker/DockerHostsTable.tsx" },
{ "path": "src/features/docker/DockerNetworksTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesAlertsTable.tsx" },
{ "path": "src/features/platformPage/PlatformResourceDetailTableRow.tsx" },
{ "path": "src/features/proxmox/ProxmoxCephTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxCoverageTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxNodesTable.tsx" },
{ "path": "src/features/truenas/TrueNASAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASProtectionTable.tsx" },
{ "path": "src/features/truenas/TrueNASServicesTable.tsx" },
{ "path": "src/features/vmware/VsphereActivityTable.tsx" },
{ "path": "src/features/vmware/VsphereAlertsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Workloads/WorkloadPanel.tsx",
"patterns": [
"p-0 border-b border-border bg-surface-alt",
"border-b border-border bg-surface-alt p-0"
]
},
{
"path": "src/features/platformPage/PlatformResourceDetailTableRow.tsx",
"patterns": ["border-b border-border bg-surface-alt p-0"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/InlineDetailTableRow.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "inline-detail-section-panel-shell",
"category": "row-detail",
"summary": "Inline platform detail content must compose DetailSectionTable or InlineDetailPanel for shared detail rows, value tones, section tables, and close actions instead of recreating local field grids or provider-named detail primitives.",
"canonical": {
"path": "src/components/shared/DetailSectionTable.tsx",
"export": "DetailSection"
},
"requiredConsumers": [
{ "path": "src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx" },
{ "path": "src/components/Infrastructure/resourceDetailDrawerKubernetesModel.ts" },
{ "path": "src/components/Infrastructure/resourceDetailDrawerTrueNASModel.ts" },
{ "path": "src/features/docker/DockerAlertsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASProtectionTable.tsx" },
{ "path": "src/features/truenas/TrueNASServicesTable.tsx" },
{ "path": "src/features/vmware/VsphereActivityTable.tsx" },
{ "path": "src/features/vmware/VsphereAlertsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Infrastructure/resourceDetailDrawerKubernetesModel.ts",
"patterns": ["makeTrueNASDetailRow", "TrueNASDetailSection", "trueNASDetailTableModel"]
},
{
"path": "src/features/docker/DockerAlertsTable.tsx",
"patterns": ["const DetailField", "XIcon class=\"h-4 w-4\""]
},
{
"path": "src/features/kubernetes/KubernetesAlertsTable.tsx",
"patterns": ["const DetailField", "XIcon class=\"h-4 w-4\""]
},
{
"path": "src/features/vmware/VsphereActivityTable.tsx",
"patterns": ["const DetailField", "XIcon class=\"h-4 w-4\""]
},
{
"path": "src/features/vmware/VsphereAlertsTable.tsx",
"patterns": ["const DetailField", "XIcon class=\"h-4 w-4\""]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/DetailSectionTable.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "table-card-frame-shell",
"category": "table-card",
"summary": "Product table surfaces must compose TableCard for the shared card frame instead of recreating local overflow-hidden bordered table wrappers.",
"canonical": {
"path": "src/components/shared/TableCard.tsx",
"export": "TableCard"
},
"requiredConsumers": [
{ "path": "src/components/Infrastructure/UnifiedResourceHostTableCard.tsx" },
{ "path": "src/components/Infrastructure/UnifiedResourceServiceInfrastructureCard.tsx" },
{ "path": "src/components/Storage/StorageContentCard.tsx" },
{ "path": "src/components/Workloads/WorkloadsSurface.tsx" },
{ "path": "src/components/Workloads/WorkloadsTable.tsx" },
{ "path": "src/features/alerts/AlertHistoryTableSection.tsx" },
{ "path": "src/features/platformPage/sharedPlatformPage.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "table-card-header-shell",
"category": "table-card",
"summary": "Table-card headers must compose TableCardHeader for title/action/clear chrome instead of recreating local summary-table header aliases.",
"canonical": {
"path": "src/components/shared/TableCardHeader.tsx",
"export": "TableCardHeader"
},
"requiredConsumers": [
{ "path": "src/components/Infrastructure/UnifiedResourceHostTableCard.tsx" },
{ "path": "src/components/Infrastructure/UnifiedResourceServiceInfrastructureCard.tsx" },
{ "path": "src/components/Storage/StorageContentCard.tsx" },
{ "path": "src/features/platformPage/sharedPlatformPage.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "compact-info-card-frame-shell",
"category": "info-card",
"summary": "Compact drawer, discovery, resource-detail, and shared overview cards must compose InfoCardFrame for the common bordered surface frame instead of recreating local card-shell classes.",
"canonical": {
"path": "src/components/shared/InfoCardFrame.tsx",
"export": "InfoCardFrame"
},
"requiredConsumers": [
{ "path": "src/components/Discovery/DiscoveryTab.tsx" },
{ "path": "src/components/Infrastructure/ResourceActionHistory.tsx" },
{ "path": "src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx" },
{ "path": "src/components/shared/WebInterfaceUrlField.tsx" },
{ "path": "src/components/shared/cards/DisksCard.tsx" },
{ "path": "src/components/shared/cards/HardwareCard.tsx" },
{ "path": "src/components/shared/cards/NetworkInterfacesCard.tsx" },
{ "path": "src/components/shared/cards/RaidCard.tsx" },
{ "path": "src/components/shared/cards/RootDiskCard.tsx" },
{ "path": "src/components/shared/cards/SystemInfoCard.tsx" },
{ "path": "src/components/shared/cards/TemperaturesCard.tsx" },
{ "path": "src/components/Workloads/DrawerDiskListCard.tsx" },
{ "path": "src/components/Workloads/GuestDrawerOverview.tsx" },
{ "path": "src/components/Workloads/NodeDrawerOverview.tsx" },
{ "path": "src/features/docker/DockerHostDrawerOverview.tsx" },
{ "path": "src/features/storageBackups/detailPresentation.ts" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/InfoCardFrame.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "settings-callout-card-shell",
"category": "callout",
"summary": "Settings informational, warning, success, and danger callouts must compose CalloutCard for shared tone, density, icon, and copy layout instead of recreating local colored panel shells.",
"canonical": {
"path": "src/components/shared/CalloutCard.tsx",
"export": "CalloutCard"
},
"requiredConsumers": [
{ "path": "src/components/Settings/AIProviderConfigurationSection.tsx" },
{
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx"
},
{ "path": "src/components/Settings/DiagnosticsResultsPanel.tsx" },
{ "path": "src/components/Settings/DiscoverySettingsForm.tsx" },
{ "path": "src/components/Settings/MonitoredSystemImpactPreview.tsx" },
{ "path": "src/components/Settings/ReportingPanel.tsx" },
{ "path": "src/components/Settings/SecurityAuthPanel.tsx" },
{ "path": "src/components/Settings/SecurityOverviewPanel.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Settings/AIProviderConfigurationSection.tsx",
"patterns": ["rounded border border-red-200", "bg-red-50 dark:bg-red-900 px-2 py-1.5"]
},
{
"path": "src/components/Settings/DiagnosticsResultsPanel.tsx",
"patterns": [
"rounded-md border border-amber-200 bg-amber-50",
"rounded-md border border-red-200 bg-red-50"
]
},
{
"path": "src/components/Settings/DiscoverySettingsForm.tsx",
"patterns": [
"rounded-md border border-amber-200 bg-amber-50/80",
"text-amber-900 dark:text-amber-100"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/CalloutCard.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "status-dot-shell",
"category": "status-indicator",
"summary": "Resource and health indicator dots must compose StatusDot for shared size, variant, pulse, title, and aria behavior instead of recreating local rounded status spans.",
"canonical": {
"path": "src/components/shared/StatusDot.tsx",
"export": "StatusDot"
},
"requiredConsumers": [
{ "path": "src/components/shared/StatusIndicatorBadge.tsx" },
{ "path": "src/components/Settings/ResourcePicker.tsx" },
{ "path": "src/components/Storage/StoragePoolDetail.tsx" },
{ "path": "src/components/shared/NodeGroupHeader.tsx" },
{ "path": "src/features/alerts/AlertOverviewStatsCards.tsx" },
{ "path": "src/features/docker/DockerHostsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesNodesTable.tsx" },
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" },
{ "path": "src/features/vmware/VsphereHostsTable.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/StatusDot.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "loading-spinner-shell",
"category": "loading-indicator",
"summary": "Patrol, AI, and shared loading indicators must compose LoadingSpinner for shared size, tone, and accessibility behavior instead of recreating border-based animate-spin spans.",
"canonical": {
"path": "src/components/shared/LoadingSpinner.tsx",
"export": "LoadingSpinner"
},
"requiredConsumers": [
{ "path": "src/components/AI/FindingsPanel.tsx" },
{ "path": "src/components/patrol/ApprovalBanner.tsx" },
{ "path": "src/components/patrol/ApprovalSection.tsx" },
{ "path": "src/components/patrol/InvestigationMessages.tsx" },
{ "path": "src/components/patrol/InvestigationSection.tsx" },
{ "path": "src/components/patrol/RunToolCallTrace.tsx" },
{ "path": "src/features/patrol/PatrolIntelligenceHeader.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/AI/FindingsPanel.tsx",
"patterns": [
"border border-current border-t-transparent animate-spin",
"h-4 w-4 border-2 border-current border-t-transparent rounded-full animate-spin"
]
},
{
"path": "src/components/patrol/ApprovalBanner.tsx",
"patterns": [
"h-3 w-3 border-2 border-white border-t-transparent rounded-full animate-spin"
]
},
{
"path": "src/components/patrol/ApprovalSection.tsx",
"patterns": [
"h-3 w-3 border-2 border-white border-t-transparent rounded-full animate-spin"
]
},
{
"path": "src/components/patrol/InvestigationMessages.tsx",
"patterns": [
"h-3 w-3 border-2 border-current border-t-transparent rounded-full animate-spin"
]
},
{
"path": "src/components/patrol/InvestigationSection.tsx",
"patterns": [
"h-3 w-3 border-2 border-current border-t-transparent rounded-full animate-spin"
]
},
{
"path": "src/components/patrol/RunToolCallTrace.tsx",
"patterns": [
"h-3 w-3 border-2 border-current border-t-transparent rounded-full animate-spin"
]
},
{
"path": "src/features/patrol/PatrolIntelligenceHeader.tsx",
"patterns": [
"animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"
]
}
],
"proof": [
"src/components/shared/LoadingSpinner.test.tsx",
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/AI/__tests__/FindingsPanel.test.ts",
"src/components/patrol/__tests__/ApprovalBanner.test.tsx",
"src/components/patrol/__tests__/ApprovalSection.test.tsx",
"src/components/patrol/__tests__/InvestigationSection.test.tsx",
"src/components/patrol/__tests__/RunToolCallTrace.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-command-shell",
"category": "action-button",
"summary": "Standard command buttons and button-styled route actions must compose Button or ButtonLink for shared variants, sizes, focus, disabled, loading, and safe-link behavior instead of recreating local Tailwind button shells.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "Button"
},
"requiredConsumers": [
{ "path": "src/components/AI/Chat/ChatMessages.tsx" },
{ "path": "src/components/Infrastructure/ResourceDetailDrawer.tsx" },
{ "path": "src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx" },
{ "path": "src/components/Settings/AgentProfilesPanel.tsx" },
{ "path": "src/components/Settings/AvailabilitySettingsPanel.tsx" },
{ "path": "src/components/Settings/BillingAdminPanel.tsx" },
{ "path": "src/components/Settings/ConnectionEditor/AddressProbeStep.tsx" },
{ "path": "src/components/Settings/ConnectionEditor/ConnectionEditor.tsx" },
{
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx"
},
{
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx"
},
{
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/VMwareCredentialSlot.tsx"
},
{ "path": "src/components/Settings/CopyCommandBlock.tsx" },
{ "path": "src/components/Settings/DataHandlingPanel.tsx" },
{ "path": "src/components/Settings/GeneralSettingsPanel.tsx" },
{ "path": "src/components/Settings/InfrastructureAgentUpdatesDialog.tsx" },
{ "path": "src/components/Settings/InfrastructureDiscoverySettingsDialog.tsx" },
{ "path": "src/components/Settings/InfrastructureInstallerSection.tsx" },
{ "path": "src/components/Settings/InfrastructureSourceManager.tsx" },
{ "path": "src/components/Settings/InfrastructureWorkspace.tsx" },
{ "path": "src/components/Settings/ReportingPanel.tsx" },
{ "path": "src/components/Settings/ResourcePicker.tsx" },
{ "path": "src/components/Workloads/GuestDrawer.tsx" },
{ "path": "src/features/patrol/PatrolIntelligenceWorkspace.tsx" },
{ "path": "src/features/standalone/StandalonePageSurface.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Settings/GeneralSettingsPanel.tsx",
"patterns": [
"inline-flex items-center rounded-md border border-border bg-surface px-3 py-2 text-xs font-medium text-base-content transition hover:bg-surface-hover"
]
},
{
"path": "src/components/Settings/BillingAdminPanel.tsx",
"patterns": [
"w-full sm:w-auto px-3 py-1.5 text-xs font-medium rounded-md border border-border bg-surface hover:bg-surface-hover disabled:opacity-50"
]
},
{
"path": "src/components/Settings/ResourcePicker.tsx",
"patterns": [
"hover:border-red-500 hover:text-red-400",
"w-full sm:w-auto min-h-10 sm:min-h-9 flex items-center justify-center gap-1.5 px-3 py-2.5 text-sm rounded-md border border-border"
]
},
{
"path": "src/components/Settings/ReportingPanel.tsx",
"patterns": [
"rounded-md border border-base-300 bg-base-100 px-4 py-2 text-sm font-medium text-base-content",
"flex w-full items-center justify-center gap-2 rounded-md px-6 py-3 font-semibold transition-all sm:w-auto",
"bg-blue-600 text-white hover:bg-blue-700",
"bg-emerald-600 text-white hover:bg-emerald-700"
]
},
{
"path": "src/components/Settings/InfrastructureInstallerSection.tsx",
"patterns": [
"inline-flex items-center justify-center rounded-md bg-emerald-600 px-3 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-700",
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900 transition-colors hover:bg-emerald-100",
"inline-flex items-center justify-center rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900 hover:bg-emerald-100",
"inline-flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium text-emerald-900 hover:bg-emerald-100"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "copy-value-action-shell",
"category": "copy-action",
"summary": "Copy-value icon and chip controls must compose CopyValueButton for shared copied-state iconography, disabled handling, focus, sizing, and aria behavior instead of recreating page-local copy buttons.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "CopyValueButton"
},
"requiredConsumers": [
{ "path": "src/components/Discovery/DiscoveryTab.tsx" },
{ "path": "src/components/shared/WebInterfaceUrlField.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Discovery/DiscoveryTab.tsx",
"patterns": [
"interface CopyValueButtonProps",
"const CopyValueButton",
"CopyIcon class=\"h-3.5 w-3.5\"",
"CheckIcon class=\"h-3.5 w-3.5 text-emerald-600"
]
},
{
"path": "src/components/shared/WebInterfaceUrlField.tsx",
"patterns": [
"CopyIcon class=\"h-3.5 w-3.5\"",
"CheckIcon class=\"h-3.5 w-3.5 text-emerald-600"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "upgrade-action-button-link",
"category": "upgrade-action",
"summary": "Button-styled upgrade CTAs must compose UpgradeButtonLink so destination routing, new-tab safety, opener preservation, tone, width, and shared button styling stay canonical.",
"canonical": {
"path": "src/components/shared/UpgradeLink.tsx",
"export": "UpgradeButtonLink"
},
"requiredConsumers": [
{ "path": "src/components/Settings/AgentProfilesPanel.tsx" },
{ "path": "src/components/Settings/AuditLogPanel.tsx" },
{ "path": "src/components/Settings/ProLicensePlanSection.tsx" },
{ "path": "src/components/shared/FeatureGateSection.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/utils/upgradePresentation.ts",
"patterns": ["getUpgradeActionButtonClass"]
},
{
"path": "src/components/Settings/AgentProfilesPanel.tsx",
"patterns": ["getUpgradeActionButtonClass"]
},
{
"path": "src/components/Settings/AuditLogPanel.tsx",
"patterns": ["getUpgradeActionButtonClass"]
},
{
"path": "src/components/shared/FeatureGateSection.tsx",
"patterns": ["getUpgradeActionButtonClass"]
},
{
"path": "src/components/Settings/ProLicensePlanSection.tsx",
"patterns": [
"inline-flex items-center gap-1 mt-3 min-h-10 sm:min-h-9 rounded-md border border-current/20 px-3 py-2 text-xs font-medium hover:bg-black/5 dark:hover:bg-white/5",
"inline-flex items-center gap-1 mt-4 min-h-10 sm:min-h-9 rounded-md border border-border px-3 py-2 text-xs font-medium text-base-content hover:bg-surface-hover"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/UpgradeLink.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "copyable-code-row-shell",
"category": "copy-action",
"summary": "Copyable code/value rows must compose CopyableCodeRow for the shared code surface and copy action instead of recreating local font-mono row shells.",
"canonical": {
"path": "src/components/shared/CopyableCodeRow.tsx",
"export": "CopyableCodeRow"
},
"requiredConsumers": [{ "path": "src/components/Discovery/DiscoveryTab.tsx" }],
"forbiddenPatterns": [
{
"path": "src/components/Discovery/DiscoveryTab.tsx",
"patterns": [
"const CopyableCodeRow",
"flex items-start gap-2 rounded bg-surface-alt px-2 py-1.5"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "form-select-shell",
"category": "form-control",
"summary": "Native select controls must compose FormSelect for shared label/id/help wiring, value synchronization, and select chrome instead of recreating labelled select wrappers locally.",
"canonical": {
"path": "src/components/shared/FormSelect.tsx",
"export": "FormSelect"
},
"requiredConsumers": [
{ "path": "src/components/AI/FindingsPanel.tsx" },
{ "path": "src/components/Alerts/EmailProviderSelect.tsx" },
{ "path": "src/components/Alerts/WebhookConfigForm.tsx" },
{ "path": "src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx" },
{ "path": "src/components/Kubernetes/K8sDeploymentsDrawer.tsx" },
{ "path": "src/components/OrgSwitcher.tsx" },
{ "path": "src/components/Settings/AIChatMaintenanceSection.tsx" },
{ "path": "src/components/Settings/AIModelSelectionSection.tsx" },
{ "path": "src/components/Settings/AIRuntimeControlsSection.tsx" },
{ "path": "src/components/Settings/AgentProfilesPanel.tsx" },
{ "path": "src/components/Settings/AuditLogPanel.tsx" },
{
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/AvailabilityTargetSlot.tsx"
},
{
"path": "src/components/Settings/ConnectionEditor/CredentialSlots/TrueNASCredentialSlot.tsx"
},
{ "path": "src/components/Settings/InfrastructureInstallerSection.tsx" },
{ "path": "src/components/Settings/NodeModalMonitoringSection.tsx" },
{ "path": "src/components/Settings/OrganizationAccessManagementSection.tsx" },
{ "path": "src/components/Settings/OrganizationAccessMembersSection.tsx" },
{ "path": "src/components/Settings/OrganizationSharingCreateSection.tsx" },
{ "path": "src/components/Settings/RecoverySettingsPanel.tsx" },
{ "path": "src/components/Settings/RolesEditorDialog.tsx" },
{ "path": "src/components/Settings/SystemLogsPanel.tsx" },
{ "path": "src/components/Settings/UpdatesSettingsPanel.tsx" },
{ "path": "src/components/Storage/DiskDetail.tsx" },
{ "path": "src/components/Storage/StoragePageControls.tsx" },
{ "path": "src/components/Storage/StoragePoolDetail.tsx" },
{ "path": "src/components/Workloads/GuestDrawerHistory.tsx" },
{ "path": "src/components/shared/FilterBar/AddFilterMenu.tsx" },
{ "path": "src/components/shared/FilterToolbar.tsx" },
{ "path": "src/features/alerts/AlertAppriseDestinationsSection.tsx" },
{ "path": "src/features/alerts/AlertEscalationSection.tsx" },
{ "path": "src/features/alerts/AlertQuietHoursSection.tsx" },
{ "path": "src/features/patrol/PatrolIntelligenceHeader.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/FormSelect.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "form-textarea-shell",
"category": "form-control",
"summary": "Native textarea controls must compose FormTextarea for shared label/id/help wiring, value synchronization, and textarea chrome instead of recreating labelled textarea wrappers locally.",
"canonical": {
"path": "src/components/shared/FormTextarea.tsx",
"export": "FormTextarea"
},
"requiredConsumers": [
{ "path": "src/components/Alerts/EmailProviderSelect.tsx" },
{ "path": "src/components/Alerts/IncidentTimelinePanel.tsx" },
{ "path": "src/components/Alerts/ThresholdsTableDockerIgnoredPrefixesSection.tsx" },
{ "path": "src/components/Alerts/WebhookConfigForm.tsx" },
{ "path": "src/components/Infrastructure/ReportMergeModal.tsx" },
{ "path": "src/components/Settings/AgentProfilesPanel.tsx" },
{ "path": "src/components/Settings/SelfHostedCommercialRecoverySection.tsx" },
{ "path": "src/components/Settings/SuggestProfileModal.tsx" },
{ "path": "src/features/alerts/AlertAppriseDestinationsSection.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/FormTextarea.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "toggle-shell",
"category": "toggle-control",
"summary": "Binary on/off controls must compose Toggle or TogglePrimitive for shared track/knob styling, disabled behavior, label/description wiring, and synthetic checked events instead of recreating local switch buttons.",
"canonical": {
"path": "src/components/shared/Toggle.tsx",
"export": "Toggle"
},
"requiredConsumers": [
{ "path": "src/components/Alerts/AlertResourceTableDesktop.tsx" },
{ "path": "src/components/Alerts/AlertResourceTableMobile.tsx" },
{ "path": "src/components/Alerts/AlertResourceTableRow.tsx" },
{ "path": "src/components/Alerts/ThresholdsTableDockerServiceGapSection.tsx" },
{ "path": "src/components/Alerts/ThresholdsTableProxmoxBackupsSection.tsx" },
{ "path": "src/components/Infrastructure/ResourceOperatorStateSection.tsx" },
{ "path": "src/components/Settings/AIRuntimeControlsSection.tsx" },
{ "path": "src/components/Settings/AISettings.tsx" },
{ "path": "src/components/Settings/AuditLogPanel.tsx" },
{ "path": "src/components/Settings/DiscoverySettingsForm.tsx" },
{ "path": "src/components/Settings/DockerRuntimeSettingsCard.tsx" },
{ "path": "src/components/Settings/GeneralSettingsPanel.tsx" },
{ "path": "src/components/Settings/NodeModalMonitoringSection.tsx" },
{ "path": "src/components/Settings/RelaySettingsPanel.tsx" },
{ "path": "src/components/Settings/SSOProvidersPanel.tsx" },
{ "path": "src/components/Settings/SecurityAuthPanel.tsx" },
{ "path": "src/features/alerts/AlertAppriseDestinationsSection.tsx" },
{ "path": "src/features/alerts/AlertCooldownSection.tsx" },
{ "path": "src/features/alerts/AlertEmailDestinationsSection.tsx" },
{ "path": "src/features/alerts/AlertEscalationSection.tsx" },
{ "path": "src/features/alerts/AlertGroupingSection.tsx" },
{ "path": "src/features/alerts/AlertQuietHoursSection.tsx" },
{ "path": "src/features/alerts/AlertRecoverySection.tsx" },
{ "path": "src/features/patrol/PatrolIntelligenceHeader.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Toggle.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "status-indicator-badge-shell",
"category": "status-indicator",
"summary": "Read-only health/state badges must compose StatusIndicatorBadge for shared status-to-tone mapping, sizing, dot wiring, and label presentation instead of recreating local badge spans with getStatusIndicatorBadgeToneClasses.",
"canonical": {
"path": "src/components/shared/StatusIndicatorBadge.tsx",
"export": "StatusIndicatorBadge"
},
"requiredConsumers": [
{ "path": "src/components/Settings/AgentProfilesPanel.tsx" },
{ "path": "src/components/Settings/DiagnosticsResultsPanel.tsx" },
{ "path": "src/components/patrol/RunHistoryEntry.tsx" },
{ "path": "src/components/patrol/PatrolStatusBar.tsx" },
{ "path": "src/features/patrol/PatrolIntelligenceSummary.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/patrol/RunHistoryEntry.tsx",
"patterns": ["runStatus.badgeClass"]
},
{
"path": "src/components/patrol/PatrolStatusBar.tsx",
"patterns": [
"lastRunStatus.badgeClass",
"rounded border border-blue-200 bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700"
]
},
{
"path": "src/features/patrol/PatrolIntelligenceSummary.tsx",
"patterns": [
"runtimeShellPresentation().badgeClass",
"inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.16em]"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/StatusIndicatorBadge.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-indicator-shell",
"category": "status-indicator",
"summary": "Platform alert tables must compose AlertSeverityBadge and AlertSeverityDot for shared severity labels, tone mapping, and status-indicator rendering instead of recreating severity helpers locally.",
"canonical": {
"path": "src/components/shared/AlertSeverityBadge.tsx",
"export": "AlertSeverityBadge"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerAlertsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASAlertsTable.tsx" },
{ "path": "src/features/vmware/VsphereAlertsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerAlertsTable.tsx",
"patterns": ["severityVariant", "severityTextClass"]
},
{
"path": "src/features/kubernetes/KubernetesAlertsTable.tsx",
"patterns": ["severityVariant", "severityTextClass"]
},
{
"path": "src/features/truenas/TrueNASAlertsTable.tsx",
"patterns": ["severityVariant", "severityTextClass"]
},
{
"path": "src/features/vmware/VsphereAlertsTable.tsx",
"patterns": ["severityVariant", "severityTextClass"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/AlertSeverityBadge.test.tsx",
"src/utils/__tests__/alertSeverityPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-filter-options",
"category": "filter-options",
"summary": "Platform alert tables must get All/Critical/Warning/Info severity filter labels, tones, and leading dots from the shared platform alert severity filter-options primitive instead of recreating local option arrays.",
"canonical": {
"path": "src/features/platformPage/platformAlertSeverityFilterOptions.tsx",
"export": "getPlatformAlertSeverityFilterOptions"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerAlertsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASAlertsTable.tsx" },
{ "path": "src/features/vmware/VsphereAlertsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerAlertsTable.tsx",
"patterns": [
"filterChipStatusDot(",
"value: 'critical'",
"value: 'warning'",
"value: 'info'"
]
},
{
"path": "src/features/kubernetes/KubernetesAlertsTable.tsx",
"patterns": [
"filterChipStatusDot(",
"value: 'critical'",
"value: 'warning'",
"value: 'info'"
]
},
{
"path": "src/features/truenas/TrueNASAlertsTable.tsx",
"patterns": ["value: 'critical'", "value: 'warning'", "value: 'info'"]
},
{
"path": "src/features/vmware/VsphereAlertsTable.tsx",
"patterns": [
"filterChipStatusDot(",
"value: 'critical'",
"value: 'warning'",
"value: 'info'"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/platformAlertSeverityFilterOptions.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-detail-tone",
"category": "detail-tone",
"summary": "Platform alert detail rows must get severity-to-detail-row tone mapping from alertSeverityPresentation instead of defining table-local alertTone helpers.",
"canonical": {
"path": "src/utils/alertSeverityPresentation.ts",
"export": "getAlertSeverityDetailTone"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerAlertsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASAlertsTable.tsx" },
{ "path": "src/features/vmware/VsphereAlertsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerAlertsTable.tsx",
"patterns": ["const alertTone", "type AlertDetailTone"]
},
{
"path": "src/features/kubernetes/KubernetesAlertsTable.tsx",
"patterns": ["const alertTone", "type AlertDetailTone"]
},
{
"path": "src/features/truenas/TrueNASAlertsTable.tsx",
"patterns": ["const alertTone", "type AlertDetailTone"]
},
{
"path": "src/features/vmware/VsphereAlertsTable.tsx",
"patterns": ["const alertTone", "type AlertDetailTone"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertSeverityPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-detail-formatters",
"category": "detail-formatting",
"summary": "Platform alert tables must get provider code, resource-type, entity-type, and timestamp labels from alertDetailPresentation instead of defining table-local detail formatter helpers.",
"canonical": {
"path": "src/utils/alertDetailPresentation.ts",
"export": "formatPlatformAlertCode"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerAlertsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesAlertsTable.tsx" },
{ "path": "src/features/truenas/TrueNASAlertsTable.tsx" },
{ "path": "src/features/vmware/VsphereAlertsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerAlertsTable.tsx",
"patterns": [
"const formatResourceType",
"const formatCode",
"const formatStartedAt",
"const detailDateTime"
]
},
{
"path": "src/features/kubernetes/KubernetesAlertsTable.tsx",
"patterns": [
"const formatResourceType",
"const formatCode",
"const formatStartedAt",
"const detailDateTime"
]
},
{
"path": "src/features/truenas/TrueNASAlertsTable.tsx",
"patterns": [
"const formatResourceType",
"const formatCode",
"const formatStartedAt",
"const detailDateTime"
]
},
{
"path": "src/features/vmware/VsphereAlertsTable.tsx",
"patterns": [
"const formatResourceType",
"const formatEntityType",
"const formatCode",
"const formatStartedAt",
"const detailDateTime"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertDetailPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "metadata-badge-shell",
"category": "metadata-badge",
"summary": "Read-only metadata chips must compose MetadataBadge so size, shape, tone vocabulary, fit behavior, and whitespace handling stay shared instead of being copied as page-local pill spans.",
"canonical": {
"path": "src/components/shared/MetadataBadge.tsx",
"export": "MetadataBadge"
},
"requiredConsumers": [
{ "path": "src/components/shared/OrganizationBadges.tsx" },
{ "path": "src/components/AI/FindingsPanel.tsx" },
{ "path": "src/components/patrol/ApprovalBanner.tsx" },
{ "path": "src/components/patrol/ApprovalSection.tsx" },
{ "path": "src/components/patrol/InvestigationSection.tsx" },
{ "path": "src/components/patrol/RunHistoryEntry.tsx" },
{ "path": "src/components/patrol/RunToolCallTrace.tsx" },
{ "path": "src/components/patrol/PatrolStatusBar.tsx" },
{ "path": "src/features/proxmox/proxmoxBackupsTableShared.tsx" },
{ "path": "src/features/patrol/PatrolIntelligenceWorkspace.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/AI/FindingsPanel.tsx",
"patterns": [
"px-1.5 py-0.5 border text-[10px] font-medium rounded",
"getFindingStatusBadgeClasses",
"getFindingSourceBadgeClasses",
"getFindingLoopStateBadgeClasses",
"getInvestigationStatusBadgeClasses",
"getInvestigationOutcomeBadgeClasses",
"getInvestigationConfidenceBadgeClasses"
]
},
{
"path": "src/components/patrol/ApprovalBanner.tsx",
"patterns": [
"px-1.5 py-0.5 text-[10px] font-medium rounded",
"firstApprovalRisk()!.badgeClass"
]
},
{
"path": "src/components/patrol/ApprovalSection.tsx",
"patterns": [
"px-1.5 py-0.5 text-[10px] font-medium rounded",
"approvalRisk.badgeClass",
"fixRisk.badgeClass",
"bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300"
]
},
{
"path": "src/components/patrol/InvestigationSection.tsx",
"patterns": [
"px-1.5 py-0.5 border text-[10px] font-medium rounded",
"getInvestigationStatusBadgeClasses",
"getInvestigationOutcomeBadgeClasses",
"rounded border border-border bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted",
"rounded bg-surface px-1.5 py-0.5 text-[10px] font-medium text-muted"
]
},
{
"path": "src/components/patrol/RunHistoryEntry.tsx",
"patterns": [
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium",
"inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-50"
]
},
{
"path": "src/components/patrol/RunToolCallTrace.tsx",
"patterns": [
"px-1.5 py-0.5 rounded text-[10px] font-medium",
"getToolCallResultBadgeClass"
]
},
{
"path": "src/components/patrol/PatrolStatusBar.tsx",
"patterns": ["text-blue-600 dark:text-blue-400\">Findings snapshot unavailable"]
},
{
"path": "src/features/proxmox/proxmoxBackupsTableShared.tsx",
"patterns": [
"inline-flex items-center rounded-sm px-1.5 py-0.5 text-[10px] font-semibold",
"presentation().badgeClassName",
"rounded-sm bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold",
"rounded-sm bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold",
"rounded-sm bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold"
]
},
{
"path": "src/features/patrol/PatrolIntelligenceWorkspace.tsx",
"patterns": [
"findingsBadgePresentation().toneClasses",
"ml-1.5 px-1.5 py-0.5 text-xs rounded-full",
"bg-surface-alt text-muted"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/MetadataBadge.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "organization-role-badge-shell",
"category": "metadata-badge",
"summary": "Organization role badges must compose OrganizationRoleBadge instead of each organization settings section mapping role tones and pill styling locally.",
"canonical": {
"path": "src/components/shared/OrganizationBadges.tsx",
"export": "OrganizationRoleBadge"
},
"requiredConsumers": [
{ "path": "src/components/Settings/OrganizationAccessInvitationsSection.tsx" },
{ "path": "src/components/Settings/OrganizationAccessMembersSection.tsx" },
{ "path": "src/components/Settings/OrganizationIncomingSharesSection.tsx" },
{ "path": "src/components/Settings/OrganizationOutgoingSharesSection.tsx" },
{ "path": "src/components/Settings/OrganizationOverviewMembersSection.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Settings/OrganizationAccessInvitationsSection.tsx",
"patterns": ["roleBadgeClass"]
},
{
"path": "src/components/Settings/OrganizationAccessMembersSection.tsx",
"patterns": ["roleBadgeClass"]
},
{
"path": "src/components/Settings/OrganizationIncomingSharesSection.tsx",
"patterns": ["roleBadgeClass"]
},
{
"path": "src/components/Settings/OrganizationOutgoingSharesSection.tsx",
"patterns": ["roleBadgeClass"]
},
{
"path": "src/components/Settings/OrganizationOverviewMembersSection.tsx",
"patterns": ["roleBadgeClass"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/OrganizationBadges.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "organization-share-status-badge-shell",
"category": "metadata-badge",
"summary": "Organization share status badges must compose OrganizationShareStatusBadge so pending/active labels and tones stay canonical across incoming and outgoing share tables.",
"canonical": {
"path": "src/components/shared/OrganizationBadges.tsx",
"export": "OrganizationShareStatusBadge"
},
"requiredConsumers": [
{ "path": "src/components/Settings/OrganizationIncomingSharesSection.tsx" },
{ "path": "src/components/Settings/OrganizationOutgoingSharesSection.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Settings/OrganizationIncomingSharesSection.tsx",
"patterns": ["statusBadgeClass"]
},
{
"path": "src/components/Settings/OrganizationOutgoingSharesSection.tsx",
"patterns": ["statusBadgeClass"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/OrganizationBadges.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-loading-state",
"category": "platform-table-loading-state",
"summary": "Platform page and table loading states must compose PlatformTableLoadingState instead of recreating local TableCard status rows.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableLoadingState"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerPageSurface.tsx" },
{ "path": "src/features/kubernetes/KubernetesPageSurface.tsx" },
{ "path": "src/features/proxmox/ProxmoxBackupsTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxPageSurface.tsx" },
{ "path": "src/features/proxmox/ProxmoxReplicationTable.tsx" },
{ "path": "src/features/standalone/StandalonePageSurface.tsx" },
{ "path": "src/features/truenas/TrueNASPageSurface.tsx" },
{ "path": "src/features/truenas/TrueNASProtectionTable.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": "platform-table-text-value-fallback",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared text-cell fallback helper for trimmed strings and canonical empty-cell markers instead of recreating local textValue helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableTextValue"
},
"requiredConsumers": [
{ "path": "src/features/kubernetes/KubernetesAutoscalingTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesClustersTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesConfigTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesControllersTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesEventsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesNetworkingTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesNodesTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesPodsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesPolicyTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesServicesTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesStorageTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/kubernetes/KubernetesAutoscalingTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesClustersTable.tsx",
"patterns": ["asTrimmedString(cluster.kubernetes?.context) || '—'"]
},
{
"path": "src/features/kubernetes/KubernetesConfigTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesControllersTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx",
"patterns": ["asTrimmedString(deployment.kubernetes?.namespace) || '—'"]
},
{
"path": "src/features/kubernetes/KubernetesEventsTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesNetworkingTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesNodesTable.tsx",
"patterns": ["asTrimmedString(meta()?.kubeletVersion) || '—'"]
},
{
"path": "src/features/kubernetes/KubernetesPodsTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesPolicyTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesServicesTable.tsx",
"patterns": ["const textValue"]
},
{
"path": "src/features/kubernetes/KubernetesStorageTable.tsx",
"patterns": ["const textValue"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-title-case-value-fallback",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared title-case fallback helper for status/state labels instead of recreating local titleCase helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableTitleCaseValue"
},
"requiredConsumers": [
{ "path": "src/features/truenas/TrueNASAppsTable.tsx" },
{ "path": "src/features/truenas/TrueNASNetworkSharesTable.tsx" },
{ "path": "src/features/truenas/TrueNASServicesTable.tsx" },
{ "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx" },
{ "path": "src/features/truenas/TrueNASVirtualMachinesTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/truenas/TrueNASAppsTable.tsx",
"patterns": ["const titleCase"]
},
{
"path": "src/features/truenas/TrueNASNetworkSharesTable.tsx",
"patterns": ["const titleCase"]
},
{
"path": "src/features/truenas/TrueNASServicesTable.tsx",
"patterns": ["const titleCase"]
},
{
"path": "src/features/truenas/TrueNASStorageTopologyTable.tsx",
"patterns": ["const titleCase"]
},
{
"path": "src/features/truenas/TrueNASVirtualMachinesTable.tsx",
"patterns": ["const titleCase"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-value-summary",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared compact value summary helper for trimmed list labels and full title text instead of recreating local compactList or summarizeValues helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "summarizePlatformTableValues"
},
"requiredConsumers": [
{ "path": "src/features/kubernetes/KubernetesAutoscalingTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesConfigTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesNetworkingTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesPolicyTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesServicesTable.tsx" },
{ "path": "src/features/truenas/TrueNASNetworkSharesTable.tsx" },
{ "path": "src/features/vmware/VsphereDatastoresTable.tsx" },
{ "path": "src/features/vmware/VsphereNetworksTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/kubernetes/KubernetesAutoscalingTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
},
{
"path": "src/features/kubernetes/KubernetesConfigTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
},
{
"path": "src/features/kubernetes/KubernetesNetworkingTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
},
{
"path": "src/features/kubernetes/KubernetesPolicyTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
},
{
"path": "src/features/kubernetes/KubernetesServicesTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
},
{
"path": "src/features/truenas/TrueNASNetworkSharesTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
},
{
"path": "src/features/vmware/VsphereDatastoresTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
},
{
"path": "src/features/vmware/VsphereNetworksTable.tsx",
"patterns": ["const compactList", "const summarizeValues"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-uptime-value-fallback",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared one-unit uptime fallback helper instead of recreating local formatUptime helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableUptimeValue"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerHostsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesNodesTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx" },
{ "path": "src/features/standalone/AgentsMachinesTable.tsx" },
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerHostsTable.tsx",
"patterns": ["const formatUptime"]
},
{
"path": "src/features/kubernetes/KubernetesNodesTable.tsx",
"patterns": ["const formatUptime"]
},
{
"path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx",
"patterns": ["const formatUptime"]
},
{
"path": "src/features/standalone/AgentsMachinesTable.tsx",
"patterns": ["const formatUptime"]
},
{
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
"patterns": ["const formatUptime"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-byte-value-fallback",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared byte-size fallback helper for positive byte formatting and canonical empty-cell markers instead of recreating local formatBytes wrappers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableBytesValue"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerNativeTableShared.tsx" },
{ "path": "src/features/kubernetes/KubernetesNodesTable.tsx" },
{ "path": "src/features/truenas/TrueNASProtectionTable.tsx" },
{ "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx" },
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" },
{ "path": "src/features/truenas/TrueNASVirtualMachinesTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerNativeTableShared.tsx",
"patterns": ["formatBytes(value)"]
},
{
"path": "src/features/kubernetes/KubernetesNodesTable.tsx",
"patterns": ["const formatBytes", "value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)"]
},
{
"path": "src/features/truenas/TrueNASProtectionTable.tsx",
"patterns": ["formatBytes(point.sizeBytes)"]
},
{
"path": "src/features/truenas/TrueNASStorageTopologyTable.tsx",
"patterns": [
"formatBytes(size)",
"formatBytes(row.resource.disk.used)",
"formatBytes(row.resource.disk.total)"
]
},
{
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
"patterns": ["const formatBytes", "value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)"]
},
{
"path": "src/features/truenas/TrueNASVirtualMachinesTable.tsx",
"patterns": ["const formatBytes", "value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-number-value-fallback",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared number-value fallback primitive for finite numeric table cells, tabular styling, and canonical empty-cell markers instead of recreating local numberValue/countCell helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableNumberValue"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerNativeTableShared.tsx" },
{ "path": "src/features/docker/DockerServicesTable.tsx" },
{ "path": "src/components/Docker/SwarmServicesDrawer.tsx" },
{ "path": "src/features/kubernetes/KubernetesAutoscalingTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesControllersTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesEventsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesPodsTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx" },
{ "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx" },
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerNativeTableShared.tsx",
"patterns": [
"typeof value === 'number' ? <span class=\"tabular-nums\">{value}</span> : <span>—</span>"
]
},
{
"path": "src/features/docker/DockerServicesTable.tsx",
"patterns": ["const replicaCount", "<span class=\"tabular-nums\">{value ?? 0}</span>"]
},
{
"path": "src/components/Docker/SwarmServicesDrawer.tsx",
"patterns": [
"<span class=\"tabular-nums\">{desired()}</span>",
"<span class=\"tabular-nums\">{running()}</span>"
]
},
{
"path": "src/features/kubernetes/KubernetesAutoscalingTable.tsx",
"patterns": [
"const numberValue",
"typeof value === 'number' ? <span class=\"tabular-nums\">{value}</span> : <span>—</span>"
]
},
{
"path": "src/features/kubernetes/KubernetesControllersTable.tsx",
"patterns": [
"const numberValue",
"typeof value === 'number' ? <span class=\"tabular-nums\">{value}</span> : <span>—</span>"
]
},
{
"path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx",
"patterns": ["const replicaCount", "<span class=\"tabular-nums\">{value ?? 0}</span>"]
},
{
"path": "src/features/kubernetes/KubernetesEventsTable.tsx",
"patterns": [
"const numberValue",
"typeof value === 'number' ? <span class=\"tabular-nums\">{value}</span> : <span>—</span>"
]
},
{
"path": "src/features/kubernetes/KubernetesPodsTable.tsx",
"patterns": [
"const numericValue",
"typeof value === 'number' ? <span class=\"tabular-nums\">{value}</span> : <span>—</span>"
]
},
{
"path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx",
"patterns": ["const countCell", "value.toLocaleString() : '—'"]
},
{
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
"patterns": ["hidden text-base-content tabular-nums lg:table-cell"]
},
{
"path": "src/features/truenas/TrueNASStorageTopologyTable.tsx",
"patterns": ["const diskCountLabel"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-count-ratio-value",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared count-ratio primitive for healthy/total or ready/total count cells instead of recreating local numerator/slash/denominator markup.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableCountRatioValue"
},
"requiredConsumers": [{ "path": "src/features/kubernetes/KubernetesClustersTable.tsx" }],
"forbiddenPatterns": [
{
"path": "src/features/kubernetes/KubernetesClustersTable.tsx",
"patterns": ["const childCountCell", "<span class=\"text-muted\">/{count.total}</span>"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-percent-value-fallback",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared percent-value primitive for one-decimal percent cells, tabular styling, and canonical empty-cell markers instead of recreating local formatPercent helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTablePercentValue"
},
"requiredConsumers": [{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" }],
"forbiddenPatterns": [
{
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
"patterns": [
"const formatPercent",
"<span class=\"tabular-nums\">{percent.toFixed(1)}%</span>"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-temperature-value-fallback",
"category": "platform-table-text-value",
"summary": "Platform tables must use the shared temperature-value primitive for positive Celsius cells, tabular styling, and canonical empty-cell markers instead of recreating local formatTemperature helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableTemperatureValue"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerHostsTable.tsx" },
{ "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx" },
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerHostsTable.tsx",
"patterns": [
"const formatTemperature",
"<span class=\"tabular-nums\">{celsius.toFixed(1)}°C</span>"
]
},
{
"path": "src/features/truenas/TrueNASStorageTopologyTable.tsx",
"patterns": ["const temperatureLabel", "${Math.round(value)}C"]
},
{
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
"patterns": [
"const formatTemperature",
"<span class=\"tabular-nums\">{celsius.toFixed(1)}°C</span>"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-metric-fallback",
"category": "platform-table-metric",
"summary": "Platform tables must use shared metric fallback and finite-metric helpers instead of recreating local centered dash fallbacks and Number.isFinite wrappers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableMetricFallback"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerContainersTable.tsx" },
{ "path": "src/features/docker/DockerHostsTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesClustersTable.tsx" },
{ "path": "src/features/kubernetes/KubernetesNodesTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxNodesTable.tsx" },
{ "path": "src/features/standalone/AgentsMachinesTable.tsx" },
{ "path": "src/features/truenas/TrueNASAppsTable.tsx" },
{ "path": "src/features/truenas/TrueNASSystemsTable.tsx" },
{ "path": "src/features/vmware/VsphereDatastoresTable.tsx" },
{ "path": "src/features/vmware/VsphereHostsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/features/docker/DockerContainersTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/docker/DockerHostsTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/kubernetes/KubernetesClustersTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/kubernetes/KubernetesNodesTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/proxmox/ProxmoxNodesTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/standalone/AgentsMachinesTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/truenas/TrueNASAppsTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/truenas/TrueNASSystemsTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/vmware/VsphereDatastoresTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
},
{
"path": "src/features/vmware/VsphereHostsTable.tsx",
"patterns": ["const finiteMetric", "const metricFallback"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-error-state",
"category": "platform-error-state",
"summary": "Platform page and table load failures must compose PlatformErrorState instead of recreating a local EmptyState plus Refresh action.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformErrorState"
},
"requiredConsumers": [
{ "path": "src/features/docker/DockerPageSurface.tsx" },
{ "path": "src/features/kubernetes/KubernetesPageSurface.tsx" },
{ "path": "src/features/proxmox/ProxmoxBackupsTable.tsx" },
{ "path": "src/features/proxmox/ProxmoxPageSurface.tsx" },
{ "path": "src/features/proxmox/ProxmoxReplicationTable.tsx" },
{ "path": "src/features/standalone/StandalonePageSurface.tsx" },
{ "path": "src/features/truenas/TrueNASPageSurface.tsx" },
{ "path": "src/features/truenas/TrueNASProtectionTable.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": "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",
"summary": "Resource-list filter surfaces must compose the shared FilterBar catalog shell instead of recreating page-local filter rows or legacy PageControls decks.",
"canonical": {
"path": "src/components/shared/FilterBar/FilterBar.tsx",
"export": "FilterBar"
},
"requiredConsumers": [
{ "path": "src/components/Alerts/ThresholdsTable.tsx" },
{ "path": "src/components/Settings/AuditLogPanel.tsx" },
{ "path": "src/components/Storage/StoragePageControls.tsx" },
{ "path": "src/components/Workloads/WorkloadsFilter.tsx" },
{ "path": "src/features/alerts/AlertHistoryFiltersCard.tsx" },
{ "path": "src/features/platformPage/sharedPlatformPage.tsx" },
{ "path": "src/features/proxmox/ProxmoxBackupsTable.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Storage/StoragePageControls.tsx",
"patterns": [
"from '@/components/shared/PageControls'",
"<PageControls",
"<StorageControls",
"<StorageFilter",
"LabeledFilterSelect",
"FilterSegmentedControl"
]
},
{
"path": "src/components/Workloads/WorkloadsFilter.tsx",
"patterns": [
"from '@/components/shared/PageControls'",
"<PageControls",
"LabeledFilterSelect",
"LabeledFilterToggleGroup"
]
},
{
"path": "src/features/proxmox/ProxmoxBackupsTable.tsx",
"patterns": [
"from '@/components/shared/PageControls'",
"<PageControls",
"LabeledFilterSelect",
"LabeledFilterToggleGroup"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/FilterBar/FilterBar.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "search-field-shell",
"category": "search-control",
"summary": "Product surfaces with simple search fields must compose SearchField for the shared icon, clear button, keyboard, focus, aria, and input chrome instead of rendering native search inputs.",
"canonical": {
"path": "src/components/shared/SearchField.tsx",
"export": "SearchField"
},
"requiredConsumers": [
{ "path": "src/components/AI/Chat/AssistantCommandHelpDialog.tsx" },
{ "path": "src/components/AI/Chat/index.tsx" },
{ "path": "src/components/Settings/InfrastructureSourcePicker.tsx" },
{ "path": "src/components/Settings/ResourcePicker.tsx" },
{ "path": "src/components/Settings/UserAssignmentsPanel.tsx" },
{ "path": "src/components/shared/AIModelPicker.tsx" },
{ "path": "src/components/shared/CommandPaletteModal.tsx" },
{ "path": "src/components/shared/SearchInput.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Settings/ResourcePicker.tsx",
"patterns": ["type=\"text\"", "onInput={(e) => setTagFilter"]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/SearchField.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "search-input-shell",
"category": "search-control",
"summary": "Resource-list and drawer search boxes that need history, tips, or type-to-search behavior must compose SearchInput instead of rebuilding search-enhancement controls locally.",
"canonical": {
"path": "src/components/shared/SearchInput.tsx",
"export": "SearchInput"
},
"requiredConsumers": [
{ "path": "src/components/Docker/SwarmServicesDrawer.tsx" },
{ "path": "src/components/Kubernetes/K8sDeploymentsDrawer.tsx" },
{ "path": "src/components/Kubernetes/K8sNamespacesDrawer.tsx" },
{ "path": "src/components/Settings/SettingsPageShell.tsx" },
{ "path": "src/components/shared/FilterBar/FilterBar.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/SearchInput.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "filter-button-group-shell",
"category": "segmented-selector",
"summary": "Settings and compact feature segmented selectors must compose FilterButtonGroup instead of recreating active-button group styling locally.",
"canonical": {
"path": "src/components/shared/FilterButtonGroup.tsx",
"export": "FilterButtonGroup"
},
"requiredConsumers": [
{ "path": "src/components/Settings/GeneralSettingsPanel.tsx" },
{ "path": "src/components/Settings/ResourcePicker.tsx" },
{ "path": "src/components/Settings/ReportingPanel.tsx" },
{ "path": "src/features/patrol/PatrolIntelligenceHeader.tsx" }
],
"forbiddenPatterns": [
{
"path": "src/components/Settings/ResourcePicker.tsx",
"patterns": [
"min-h-10 sm:min-h-9 min-w-10 px-3 py-2 rounded-md text-sm font-medium transition-all",
"<For each={RESOURCE_PICKER_TYPE_FILTERS}>"
]
}
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/FilterButtonGroup.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "chart-visibility-toggle-button",
"category": "display-toggle",
"summary": "Summary-bearing filter surfaces must compose ChartVisibilityToggleButton for chart show/hide display actions instead of recreating one-off chart toggle buttons.",
"canonical": {
"path": "src/components/shared/FilterToolbar.tsx",
"export": "ChartVisibilityToggleButton"
},
"requiredConsumers": [{ "path": "src/components/Workloads/WorkloadsFilter.tsx" }],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/FilterToolbar.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "column-picker-shell",
"category": "column-picker",
"summary": "Table column visibility controls must compose ColumnPicker instead of recreating column chooser trigger, panel, reset, or hidden-count chrome locally.",
"canonical": {
"path": "src/components/shared/ColumnPicker.tsx",
"export": "ColumnPicker"
},
"requiredConsumers": [
{ "path": "src/components/Workloads/WorkloadsFilter.tsx" },
{ "path": "src/features/standalone/AgentsMachinesTable.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/ColumnPicker.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "selection-card-group-shell",
"category": "selection-card-group",
"summary": "Selectable settings card groups must compose SelectionCardGroup instead of recreating compact/detail active-card styling and disabled selection behavior locally.",
"canonical": {
"path": "src/components/shared/SelectionCardGroup.tsx",
"export": "SelectionCardGroup"
},
"requiredConsumers": [
{ "path": "src/components/Settings/AISettingsDialogs.tsx" },
{ "path": "src/components/Settings/UpdatesSettingsPanel.tsx" }
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/SelectionCardGroup.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "grouped-table-mode-segmented-control",
"category": "table-mode-control",
"summary": "Grouped/list table-mode controls must compose GroupedTableModeSegmentedControl instead of recreating grouped/list labels, titles, or icons locally.",
"canonical": {
"path": "src/components/shared/GroupedTableModeSegmentedControl.tsx",
"export": "GroupedTableModeSegmentedControl"
},
"requiredConsumers": [{ "path": "src/components/Workloads/WorkloadsFilter.tsx" }],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
}
],
"patternGuards": [
{
"id": "patrol-ai-local-loading-spinner-shell",
"category": "loading-indicator",
"summary": "Patrol and AI finding surfaces must not recreate border-based loading spinners locally; compose LoadingSpinner so size, tone, and accessibility behavior stay shared.",
"canonical": {
"path": "src/components/shared/LoadingSpinner.tsx",
"export": "LoadingSpinner"
},
"scopes": [
"src/components/patrol",
"src/features/patrol",
"src/components/AI/FindingsPanel.tsx"
],
"extensions": [".tsx"],
"allPatterns": ["border-t-transparent", "animate-spin"],
"legacyReason": "Retired migration debt. Patrol action-pending, investigation-loading, header-saving, and AI finding-loading spinners compose LoadingSpinner for shared shell, sizing, tone, and accessible status behavior.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/LoadingSpinner.test.tsx",
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-shell-local-frame",
"category": "platform-table-shell",
"summary": "Platform tables must compose the shared PlatformTableShell frame instead of recreating the TableCard/TableHeader/TableBody shell locally.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableShell"
},
"scopes": ["src/features", "src/components"],
"extensions": [".tsx"],
"allPatterns": [
"TableCard class={PLATFORM_TABLE_CARD_CLASS}",
"TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}",
"TableBody class={PLATFORM_TABLE_BODY_CLASS}"
],
"legacyReason": "Retired migration debt. Platform table frames must use PlatformTableShell; no local-frame exceptions are currently allowed.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-empty-state-local-empty-state",
"category": "platform-table-empty-state",
"summary": "Platform feature tables must compose PlatformTableEmptyState for their table/card empty-state shell instead of importing EmptyState directly.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableEmptyState"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["@/components/shared/EmptyState"],
"legacyReason": "Retired migration debt. Platform table empty states must use PlatformTableEmptyState; no platform feature exceptions are currently allowed.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "table-card-frame-local-wrapper",
"category": "table-card",
"summary": "Product table surfaces must not copy the retired local bordered overflow-hidden table wrapper owned by TableCard.",
"canonical": {
"path": "src/components/shared/TableCard.tsx",
"export": "TableCard"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["overflow-hidden border-border-subtle bg-surface"],
"legacyReason": "Retired migration debt. Product table frames must use TableCard; local bordered overflow-hidden table wrappers are not allowed.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "table-card-header-local-summary-header",
"category": "table-card",
"summary": "Product table surfaces must not reintroduce the retired SummaryTableCardHeader local alias.",
"canonical": {
"path": "src/components/shared/TableCardHeader.tsx",
"export": "TableCardHeader"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["SummaryTableCardHeader"],
"legacyReason": "Retired migration debt. Table-card title/action/clear chrome must use TableCardHeader; no local header aliases are allowed.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "compact-info-card-frame-local-shell",
"category": "info-card",
"summary": "Compact drawer, discovery, resource-detail, and shared overview cards must not copy the bordered bg-surface p-3 shadow shell owned by InfoCardFrame.",
"canonical": {
"path": "src/components/shared/InfoCardFrame.tsx",
"export": "InfoCardFrame"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".ts", ".tsx"],
"allPatterns": ["rounded border border-border bg-surface p-3 shadow-sm"],
"legacyReason": "Retired migration debt. Repeated compact information card frames must use InfoCardFrame; local card-shell copies are not allowed.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/InfoCardFrame.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-table-loading-state-local-status-row",
"category": "platform-table-loading-state",
"summary": "Platform feature loading states must compose PlatformTableLoadingState instead of recreating the compact TableCard status row locally.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableLoadingState"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["role=\"status\"", "px-3 py-2 text-xs text-muted"],
"legacyReason": "Retired migration debt. Platform table loading states must use PlatformTableLoadingState; no compact local status-row exceptions are currently allowed.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "kubernetes-platform-table-local-text-value-helper",
"category": "platform-table-text-value",
"summary": "Kubernetes platform tables must not recreate local textValue helpers for trimmed-string empty-cell fallback.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableTextValue"
},
"scopes": ["src/features/kubernetes"],
"extensions": [".tsx"],
"allPatterns": ["const textValue", "asTrimmedString(value) || '—'"],
"legacyReason": "Retired migration debt. Kubernetes platform table text cells must use formatPlatformTableTextValue for the shared trimmed-string empty-cell marker.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "truenas-platform-table-local-title-case-helper",
"category": "platform-table-text-value",
"summary": "TrueNAS platform tables must not recreate local titleCase helpers for status/state label fallback.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableTitleCaseValue"
},
"scopes": ["src/features/truenas"],
"extensions": [".tsx"],
"allPatterns": ["const titleCase", "asTrimmedString(value)", "return 'Unknown'"],
"legacyReason": "Retired migration debt. TrueNAS platform table status/state labels must use formatPlatformTableTitleCaseValue for the shared title-case fallback.",
"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-compact-list-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local compactList helpers for compact value summaries.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "summarizePlatformTableValues"
},
"scopes": ["src/features/kubernetes", "src/features/truenas", "src/features/vmware"],
"extensions": [".tsx"],
"allPatterns": ["const compactList"],
"legacyReason": "Retired migration debt. Platform table compact list normalization must use summarizePlatformTableValues for shared trimming and normalized values.",
"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-value-summary-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local summarizeValues helpers for compact value summaries.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "summarizePlatformTableValues"
},
"scopes": ["src/features/kubernetes", "src/features/truenas", "src/features/vmware"],
"extensions": [".tsx"],
"allPatterns": ["const summarizeValues"],
"legacyReason": "Retired migration debt. Platform table compact value summaries must use summarizePlatformTableValues for shared labels, overflow counts, and title text.",
"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-uptime-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local one-unit formatUptime helpers for uptime label fallback.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableUptimeValue"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas"
],
"extensions": [".tsx"],
"allPatterns": ["const formatUptime", "seconds / 86_400", "return `${mins}m`"],
"legacyReason": "Retired migration debt. Platform table uptime labels must use formatPlatformTableUptimeValue for the shared one-unit uptime fallback.",
"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-bytes-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local formatBytes wrappers for positive byte-size empty-cell fallback.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableBytesValue"
},
"scopes": ["src/features/docker", "src/features/kubernetes", "src/features/truenas"],
"extensions": [".tsx"],
"allPatterns": ["const formatBytes", "value.toFixed(value >= 100 ? 0 : value >= 10 ? 1 : 2)"],
"legacyReason": "Retired migration debt. Platform table byte-size cells must use formatPlatformTableBytesValue for shared positive-byte formatting and empty-cell markers.",
"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-format-bytes-import",
"category": "platform-table-text-value",
"summary": "Platform feature tables must not import the generic byte formatter directly for dense table byte cells.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "formatPlatformTableBytesValue"
},
"scopes": ["src/features/truenas"],
"extensions": [".tsx"],
"allPatterns": ["import { formatBytes } from '@/utils/format';", "formatBytes("],
"legacyReason": "Retired migration debt. Dense platform table byte cells must use formatPlatformTableBytesValue so positive-byte formatting and empty markers 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-number-value-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local numberValue helpers for optional numeric table cells.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableNumberValue"
},
"scopes": ["src/features/docker", "src/features/kubernetes"],
"extensions": [".tsx"],
"allPatterns": ["<span class=\"tabular-nums\">{value}</span>", "<span>—</span>"],
"legacyReason": "Retired migration debt. Optional numeric platform table cells must use PlatformTableNumberValue for finite-number checks, tabular styling, and empty-cell markers.",
"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-count-cell-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local countCell helpers for locale-formatted optional numeric table cells.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableNumberValue"
},
"scopes": ["src/features/proxmox"],
"extensions": [".tsx"],
"allPatterns": ["const countCell", "value.toLocaleString() : '—'"],
"legacyReason": "Retired migration debt. Locale-formatted count cells must compose PlatformTableNumberValue with a caller-owned format function instead of owning fallback markup locally.",
"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-replica-count-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local replicaCount helpers for scheduler/service count columns.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableNumberValue"
},
"scopes": ["src/features/docker", "src/features/kubernetes"],
"extensions": [".tsx"],
"allPatterns": ["const replicaCount", "<span class=\"tabular-nums\">{value ?? 0}</span>"],
"legacyReason": "Retired migration debt. Scheduler and service count cells must compose PlatformTableNumberValue with caller-owned zero-default semantics instead of local tabular span helpers.",
"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-swarm-count-spans",
"category": "platform-table-text-value",
"summary": "Docker Swarm table drawers must not render desired/running service counts with local tabular spans.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableNumberValue"
},
"scopes": ["src/components/Docker"],
"extensions": [".tsx"],
"allPatterns": ["desiredTasks", "runningTasks", "class=\"tabular-nums\""],
"legacyReason": "Retired migration debt. Swarm desired/running count cells must route through PlatformTableNumberValue so drawer tables and platform tables share numeric cell rendering.",
"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-count-ratio-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local healthy/total count-ratio helpers.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableCountRatioValue"
},
"scopes": ["src/features/kubernetes"],
"extensions": [".tsx"],
"allPatterns": ["const childCountCell", "<span class=\"text-muted\">/{count.total}</span>"],
"legacyReason": "Retired migration debt. Healthy/total and ready/total table count cells must compose PlatformTableCountRatioValue so ratio markup, tabular styling, muted denominator styling, and empty markers 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",
"summary": "TrueNAS table scalar value cells must not put tabular-number styling on table cells instead of shared value primitives.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableNumberValue"
},
"scopes": ["src/features/truenas"],
"extensions": [".tsx"],
"allPatterns": ["TrueNAS", "hidden text-base-content tabular-nums lg:table-cell"],
"legacyReason": "Retired migration debt. TrueNAS share/service and adjacent scalar value cells must render through shared platform value primitives so finite-number checks, tabular styling, and empty markers 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-percent-value-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local formatPercent helpers for one-decimal percent cells.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTablePercentValue"
},
"scopes": ["src/features/truenas"],
"extensions": [".tsx"],
"allPatterns": [
"const formatPercent",
"<span class=\"tabular-nums\">{percent.toFixed(1)}%</span>"
],
"legacyReason": "Retired migration debt. One-decimal percent cells must compose PlatformTablePercentValue so percent formatting, tabular styling, and empty markers 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-temperature-value-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local formatTemperature helpers for positive Celsius cells.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableTemperatureValue"
},
"scopes": ["src/features/docker", "src/features/truenas"],
"extensions": [".tsx"],
"allPatterns": [
"const formatTemperature",
"<span class=\"tabular-nums\">{celsius.toFixed(1)}°C</span>"
],
"legacyReason": "Retired migration debt. Positive Celsius table cells must compose PlatformTableTemperatureValue so validity, formatting, tabular styling, and empty markers 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-temperature-label-helper",
"category": "platform-table-text-value",
"summary": "Platform tables must not recreate local temperatureLabel helpers for positive Celsius cells.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableTemperatureValue"
},
"scopes": ["src/features/truenas"],
"extensions": [".tsx"],
"allPatterns": ["const temperatureLabel", "${Math.round(value)}C"],
"legacyReason": "Retired migration debt. Positive Celsius table cells must compose PlatformTableTemperatureValue so validity, formatting, tabular styling, and empty markers 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-metric-fallback-helper",
"category": "platform-table-metric",
"summary": "Platform tables must not recreate local finiteMetric and metricFallback helpers for metric cells.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableMetricFallback"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["const finiteMetric", "const metricFallback", "Number.isFinite(value)"],
"legacyReason": "Retired migration debt. Platform table metric cells must use PlatformTableMetricFallback and getPlatformTableFiniteMetric instead of carrying page-local metric fallback helpers.",
"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-metric-empty-marker",
"category": "platform-table-metric",
"summary": "Platform metric cells must not recreate centered muted dash fallback markup inline.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformTableMetricFallback"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["flex justify-center", "text-xs text-muted", "aria-hidden=\"true\""],
"legacyReason": "Retired migration debt. Platform metric cells must compose PlatformTableMetricFallback instead of carrying inline centered dash fallback markup.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/sharedPlatformPage.test.ts",
"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": "chart-visibility-local-toggle-labels",
"category": "display-toggle",
"summary": "Summary-bearing surfaces must not copy the Show charts / Hide charts display-toggle labels owned by ChartVisibilityToggleButton.",
"canonical": {
"path": "src/components/shared/FilterToolbar.tsx",
"export": "ChartVisibilityToggleButton"
},
"scopes": ["src/components/Workloads", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["Show charts", "Hide charts"],
"legacyReason": "Retired migration debt. Chart visibility display actions must use ChartVisibilityToggleButton; local show/hide chart toggle copies are not allowed.",
"allowedPaths": [],
"ignoredPaths": ["src/components/Workloads/__tests__/WorkloadsFilter.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "column-picker-local-column-chooser",
"category": "column-picker",
"summary": "Table surfaces must not copy the column chooser title and panel copy owned by ColumnPicker.",
"canonical": {
"path": "src/components/shared/ColumnPicker.tsx",
"export": "ColumnPicker"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["Choose which columns to display", "Show Columns"],
"legacyReason": "Retired migration debt. Table column visibility controls must use ColumnPicker; local column chooser copies are not allowed.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/ColumnPicker.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "selection-card-group-local-active-card-styles",
"category": "selection-card-group",
"summary": "Settings and feature surfaces must not copy the compact/detail active-card styling owned by SelectionCardGroup.",
"canonical": {
"path": "src/components/shared/selectionCardGroupModel.ts",
"export": "getSelectionCardButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"p-4 rounded-md border-2 transition-all text-left",
"p-3 rounded-md border-2 transition-all text-center"
],
"legacyReason": "Retired migration debt. Selectable settings card groups must use SelectionCardGroup; local compact/detail active-card styling forks are not allowed.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "grouped-table-mode-local-segmented-control",
"category": "table-mode-control",
"summary": "Resource surfaces must not copy the grouped/list table-mode segmented-control labels owned by GroupedTableModeSegmentedControl.",
"canonical": {
"path": "src/components/shared/GroupedTableModeSegmentedControl.tsx",
"export": "GroupedTableModeSegmentedControl"
},
"scopes": ["src/components", "src/features"],
"extensions": [".tsx"],
"allPatterns": ["Grouped table view", "Flat list view"],
"legacyReason": "Retired migration debt. Grouped/list table-mode controls must use GroupedTableModeSegmentedControl; no local label/title copies 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",
"summary": "Page and feature filter surfaces must not import the legacy PageControls deck when they should be expressed as a FilterBar catalog.",
"canonical": {
"path": "src/components/shared/FilterBar/FilterBar.tsx",
"export": "FilterBar"
},
"scopes": ["src/components", "src/features"],
"extensions": [".tsx"],
"allPatterns": ["@/components/shared/PageControls"],
"legacyReason": "Retired migration debt. Resource-list filter surfaces must compose FilterBar; no page-level filter exceptions are currently allowed.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "search-control-native-search-input",
"category": "search-control",
"summary": "Product surfaces must not render native type=search inputs; they must compose SearchField or SearchInput so search chrome, clear behavior, keyboard handling, and aria labels stay canonical.",
"canonical": {
"path": "src/components/shared/SearchField.tsx",
"export": "SearchField"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["type=\"search\""],
"legacyReason": "Retired migration debt. Search controls must use SearchField or SearchInput; native search inputs are not allowed in product surfaces.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "status-dot-local-linked-disk-health-dot",
"category": "status-indicator",
"summary": "Storage linked-disk health rows must not recreate the retired green/yellow raw dot classes owned by StatusDot.",
"canonical": {
"path": "src/components/shared/StatusDot.tsx",
"export": "StatusDot"
},
"scopes": ["src/components/Storage", "src/features/storageBackups"],
"extensions": [".ts", ".tsx"],
"allPatterns": ["h-2 w-2 rounded-full bg-yellow-500", "h-2 w-2 rounded-full bg-green-500"],
"legacyReason": "Retired migration debt. Linked-disk health indicators must use StatusDot variants instead of local green/yellow dot classes.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "status-indicator-badge-local-tone-helper",
"category": "status-indicator",
"summary": "Product components must compose StatusIndicatorBadge instead of calling getStatusIndicatorBadgeToneClasses directly for read-only health/state badges.",
"canonical": {
"path": "src/components/shared/StatusIndicatorBadge.tsx",
"export": "StatusIndicatorBadge"
},
"scopes": ["src/components", "src/features"],
"extensions": [".tsx"],
"allPatterns": ["getStatusIndicatorBadgeToneClasses("],
"legacyReason": "Retired migration debt. Read-only status badges must route through StatusIndicatorBadge; status utilities may still expose tone classes for the shared primitive and non-component tests.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/StatusIndicatorBadge.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-local-helper",
"category": "status-indicator",
"summary": "Platform alert tables must not recreate severityVariant/severityTextClass helpers; alert severity tone and label presentation belongs to AlertSeverityBadge and alertSeverityPresentation.",
"canonical": {
"path": "src/components/shared/AlertSeverityBadge.tsx",
"export": "AlertSeverityBadge"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["severityVariant", "severityTextClass"],
"legacyReason": "Retired migration debt. Platform alert rows must compose AlertSeverityBadge and AlertSeverityDot instead of page-local severity tone helpers.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/AlertSeverityBadge.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-local-detail-tone-helper",
"category": "detail-tone",
"summary": "Platform alert tables must not recreate alertTone severity detail-row helpers; detail tone mapping belongs to alertSeverityPresentation.",
"canonical": {
"path": "src/utils/alertSeverityPresentation.ts",
"export": "getAlertSeverityDetailTone"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["const alertTone", "severityBucket"],
"legacyReason": "Retired migration debt. Platform alert detail rows must call getAlertSeverityDetailTone instead of carrying page-local severity tone helpers.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertSeverityPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-local-code-formatter-helper",
"category": "detail-formatting",
"summary": "Platform alert tables must not recreate provider-code formatter helpers; alert code labels belong to alertDetailPresentation.",
"canonical": {
"path": "src/utils/alertDetailPresentation.ts",
"export": "formatPlatformAlertCode"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["const formatCode", "IncidentRow"],
"legacyReason": "Retired migration debt. Platform alert code labels must call formatPlatformAlertCode instead of carrying page-local prefix stripping and title casing.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertDetailPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-local-resource-type-formatter-helper",
"category": "detail-formatting",
"summary": "Platform alert tables must not recreate resource-type formatter helpers; provider-specific resource labels belong to alertDetailPresentation.",
"canonical": {
"path": "src/utils/alertDetailPresentation.ts",
"export": "formatPlatformAlertResourceType"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["const formatResourceType", "IncidentRow"],
"legacyReason": "Retired migration debt. Platform alert resource-type labels must call formatPlatformAlertResourceType instead of carrying page-local switch maps.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertDetailPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-local-started-at-formatter-helper",
"category": "detail-formatting",
"summary": "Platform alert tables must not recreate started-at timestamp formatter helpers; alert timestamp labels belong to alertDetailPresentation.",
"canonical": {
"path": "src/utils/alertDetailPresentation.ts",
"export": "formatPlatformAlertStartedAt"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["const formatStartedAt", "IncidentRow"],
"legacyReason": "Retired migration debt. Platform alert row timestamps must call formatPlatformAlertStartedAt instead of carrying page-local date formatting.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertDetailPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-local-detail-date-formatter-helper",
"category": "detail-formatting",
"summary": "Platform alert tables must not recreate detail timestamp formatter helpers; alert detail date labels belong to alertDetailPresentation.",
"canonical": {
"path": "src/utils/alertDetailPresentation.ts",
"export": "formatPlatformAlertDetailDateTime"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": ["const detailDateTime", "IncidentRow"],
"legacyReason": "Retired migration debt. Platform alert detail timestamps must call formatPlatformAlertDetailDateTime instead of carrying page-local date formatting.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertDetailPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-local-entity-type-formatter-helper",
"category": "detail-formatting",
"summary": "vSphere alert tables must not recreate entity-type formatter helpers; vSphere alert entity labels belong to alertDetailPresentation.",
"canonical": {
"path": "src/utils/alertDetailPresentation.ts",
"export": "formatPlatformAlertEntityType"
},
"scopes": ["src/features/vmware"],
"extensions": [".tsx"],
"allPatterns": ["const formatEntityType", "VmwareIncidentRow"],
"legacyReason": "Retired migration debt. vSphere alert entity labels must call formatPlatformAlertEntityType instead of carrying page-local normalization.",
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertDetailPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "patrol-finding-metadata-badge-local-shell",
"category": "metadata-badge",
"summary": "Patrol finding-row metadata chips must compose MetadataBadge instead of recreating local bordered xs badge spans.",
"canonical": {
"path": "src/components/shared/MetadataBadge.tsx",
"export": "MetadataBadge"
},
"scopes": ["src/components/AI"],
"extensions": [".tsx"],
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded"],
"legacyReason": "Retired migration debt. Patrol finding row state, source, severity, investigation, confidence, and regression chips use MetadataBadge for shared shell, sizing, tone, and outline behavior.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/MetadataBadge.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "patrol-investigation-metadata-badge-local-shell",
"category": "metadata-badge",
"summary": "Patrol investigation metadata chips must compose MetadataBadge instead of recreating local bordered xs status, outcome, record, or tool badges.",
"canonical": {
"path": "src/components/shared/MetadataBadge.tsx",
"export": "MetadataBadge"
},
"scopes": ["src/components/patrol"],
"extensions": [".tsx"],
"allPatterns": ["px-1.5 py-0.5 border text-[10px] font-medium rounded"],
"legacyReason": "Retired migration debt. Patrol investigation status, outcome, durable-record, and tool chips use MetadataBadge for shared shell, sizing, tone, and outline behavior.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/patrol/__tests__/InvestigationSection.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "patrol-approval-tool-call-metadata-badge-local-shell",
"category": "metadata-badge",
"summary": "Patrol approval and tool-call metadata chips must compose MetadataBadge instead of recreating local xs risk, expiry, or result badges.",
"canonical": {
"path": "src/components/shared/MetadataBadge.tsx",
"export": "MetadataBadge"
},
"scopes": ["src/components/patrol"],
"extensions": [".tsx"],
"allPatterns": ["px-1.5 py-0.5", "text-[10px] font-medium rounded"],
"legacyReason": "Retired migration debt. Patrol approval risk, approval state, and tool-call result chips use MetadataBadge for shared shell, sizing, tone, and whitespace behavior.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/patrol/__tests__/ApprovalSection.test.tsx",
"src/components/patrol/__tests__/RunToolCallTrace.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "patrol-run-metadata-badge-local-shell",
"category": "metadata-badge",
"summary": "Patrol run-history metadata chips must compose MetadataBadge instead of recreating local rounded resource or outcome pill spans.",
"canonical": {
"path": "src/components/shared/MetadataBadge.tsx",
"export": "MetadataBadge"
},
"scopes": ["src/components/patrol"],
"extensions": [".tsx"],
"allPatterns": ["inline-flex items-center gap-1", "rounded-full text-xs font-medium"],
"legacyReason": "Retired migration debt. Patrol run-history resource and outcome chips use MetadataBadge for shared shell, sizing, tone, and whitespace behavior.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/MetadataBadge.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "proxmox-backup-metadata-badge-local-shell",
"category": "metadata-badge",
"summary": "Proxmox backup source and state metadata chips must compose MetadataBadge instead of recreating local rounded-sm xs badge spans.",
"canonical": {
"path": "src/components/shared/MetadataBadge.tsx",
"export": "MetadataBadge"
},
"scopes": ["src/features/proxmox"],
"extensions": [".tsx"],
"allPatterns": ["rounded-sm px-1.5 py-0.5 text-[10px] font-semibold"],
"legacyReason": "Retired migration debt. Proxmox backup source/state chips use MetadataBadge for shared shell, sizing, tone vocabulary, and whitespace behavior.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/proxmox/__tests__/proxmoxBackupsTableShared.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "organization-role-badge-local-shell",
"category": "metadata-badge",
"summary": "Organization settings sections must not call roleBadgeClass directly; compose OrganizationRoleBadge so role tone mapping and badge shell stay canonical.",
"canonical": {
"path": "src/components/shared/OrganizationBadges.tsx",
"export": "OrganizationRoleBadge"
},
"scopes": ["src/components/Settings"],
"extensions": [".tsx"],
"allPatterns": ["roleBadgeClass("],
"legacyReason": "Retired migration debt. Organization role badges belong to OrganizationRoleBadge and MetadataBadge.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/OrganizationBadges.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "organization-share-status-badge-local-shell",
"category": "metadata-badge",
"summary": "Organization share tables must not recreate local statusBadgeClass helpers; compose OrganizationShareStatusBadge for canonical pending/active labels and tones.",
"canonical": {
"path": "src/components/shared/OrganizationBadges.tsx",
"export": "OrganizationShareStatusBadge"
},
"scopes": ["src/components/Settings"],
"extensions": [".tsx"],
"allPatterns": ["statusBadgeClass("],
"legacyReason": "Retired migration debt. Organization share status badges belong to OrganizationShareStatusBadge and MetadataBadge.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/OrganizationBadges.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "form-select-local-native-select",
"category": "form-control",
"summary": "Product and shared filter components must compose FormSelect instead of recreating labelled native select shells locally.",
"canonical": {
"path": "src/components/shared/FormSelect.tsx",
"export": "FormSelect"
},
"scopes": ["src/components", "src/features"],
"extensions": [".tsx"],
"allPatterns": ["<select"],
"legacyReason": "Retired migration debt. Native select controls must use FormSelect for label/id/select chrome wiring and dynamic option value synchronization.",
"allowedPaths": [],
"ignoredPaths": [
"src/components/Infrastructure/__tests__/ResourceDetailDrawer.docker-container.test.tsx",
"src/components/Infrastructure/__tests__/ResourceDetailDrawer.machine-history.test.tsx",
"src/components/shared/FilterToolbar.test.tsx",
"src/components/shared/SharedPrimitives.guardrails.test.ts"
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "form-textarea-local-alert-fields",
"category": "form-control",
"summary": "Alert destination and incident timeline textareas must compose FormTextarea; only the resource note editors remain as bounded residuals until their editor-specific migration.",
"canonical": {
"path": "src/components/shared/FormTextarea.tsx",
"export": "FormTextarea"
},
"scopes": ["src/components/Alerts", "src/features/alerts"],
"extensions": [".tsx"],
"allPatterns": ["<textarea"],
"legacyReason": "Bounded residual. Alert resource note editors still own row/mobile-specific editing behavior; new alert textareas must use FormTextarea.",
"allowedPaths": [
"src/components/Alerts/AlertResourceTableMobile.tsx",
"src/components/Alerts/AlertResourceTableRow.tsx"
],
"ignoredPaths": [
"src/components/Alerts/EmailProviderSelect.test.tsx",
"src/components/shared/SharedPrimitives.guardrails.test.ts"
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "form-textarea-local-settings-fields",
"category": "form-control",
"summary": "Settings textareas must compose FormTextarea; the SSO provider editor remains as bounded residual until its multi-field provider metadata editor is migrated.",
"canonical": {
"path": "src/components/shared/FormTextarea.tsx",
"export": "FormTextarea"
},
"scopes": ["src/components/Settings"],
"extensions": [".tsx"],
"allPatterns": ["<textarea"],
"legacyReason": "Bounded residual. SSO provider metadata textareas still require a dedicated editor migration; new settings textareas must use FormTextarea.",
"allowedPaths": ["src/components/Settings/SSOProvidersPanel.tsx"],
"ignoredPaths": [
"src/components/Settings/__tests__/ProLicensePanel.test.tsx",
"src/components/shared/SharedPrimitives.guardrails.test.ts"
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "form-textarea-local-infrastructure-fields",
"category": "form-control",
"summary": "Infrastructure modal textareas must compose FormTextarea instead of recreating labelled native textarea wrappers locally.",
"canonical": {
"path": "src/components/shared/FormTextarea.tsx",
"export": "FormTextarea"
},
"scopes": ["src/components/Infrastructure"],
"extensions": [".tsx"],
"allPatterns": ["<textarea"],
"legacyReason": "Retired migration debt. Infrastructure form textareas must use FormTextarea for label/id/help wiring.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "toggle-local-role-switch",
"category": "toggle-control",
"summary": "Binary switch-looking controls must compose Toggle or TogglePrimitive instead of hand-rolling local role=switch buttons with aria-checked and custom track/knob classes.",
"canonical": {
"path": "src/components/shared/Toggle.tsx",
"export": "Toggle"
},
"scopes": ["src/components", "src/features"],
"extensions": [".tsx"],
"allPatterns": ["role=\"switch\"", "aria-checked"],
"legacyReason": "Retired migration debt. Local switch role buttons must route through the shared Toggle primitive; radios, checkboxes, and selection controls are governed separately.",
"allowedPaths": [],
"ignoredPaths": [
"src/components/Alerts/ResourceTable.test.tsx",
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Toggle.test.tsx"
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Toggle.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "filter-chip-status-dot-local-factory",
"category": "filter-bar",
"summary": "Filter option status-dot leading icons must use the FilterBar presentation helper instead of page-local span factories.",
"canonical": {
"path": "src/components/shared/FilterBar/filterOptionPresentation.tsx",
"export": "filterChipStatusDot"
},
"scopes": ["src/components", "src/features"],
"extensions": [".tsx"],
"allPatterns": ["h-2 w-2 rounded-full ${className}"],
"legacyReason": "Retired migration debt. Filter chip leading-dot presentation is owned by the FilterBar primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "filter-button-group-local-segmented-control-styles",
"category": "segmented-selector",
"summary": "Settings and feature surfaces must not copy the compact active-button segmented-control styling owned by FilterButtonGroup.",
"canonical": {
"path": "src/components/shared/filterButtonGroupModel.ts",
"export": "getFilterButtonGroupButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"flex items-center bg-base rounded-md p-1 border shadow-inner",
"flex-1 py-1.5 px-2 text-xs font-semibold rounded-md transition-all duration-200"
],
"legacyReason": "Retired migration debt. Settings and compact feature segmented selectors must use FilterButtonGroup; local active-button selector styling forks are not allowed.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-secondary-command-local-shell",
"category": "action-button",
"summary": "Product surfaces must not copy the standard secondary command-button shell; use Button for buttons and ButtonLink for route/link actions.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content"
],
"legacyReason": "Retired migration debt. Standard secondary command buttons belong to the shared Button primitive family, including button-styled links.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-command-copy-local-shell",
"category": "action-button",
"summary": "Command-copy controls must not recreate the absolute top-right icon-button shell; use CommandCopyButton from the shared Button primitive family.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "CommandCopyButton"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"absolute right-2 top-2",
"items-center justify-center",
"bg-surface-hover p-2"
],
"legacyReason": "Retired migration debt. Command-copy icon buttons belong to CommandCopyButton so position, sizing, icon, focus, disabled, and aria behavior stay canonical.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-drawer-header-action-local-shell",
"category": "action-button",
"summary": "Drawer header command actions must compose DrawerHeaderActionButton for shared height, density, focus, disabled, and hover styling instead of copying drawer-local button classes.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "DrawerHeaderActionButton"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"inline-flex h-8 items-center gap-1.5 rounded border border-border bg-surface px-2 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
],
"legacyReason": "Retired migration debt. Drawer header Assistant and context-copy actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-drawer-header-icon-local-shell",
"category": "action-button",
"summary": "Drawer header icon actions must compose DrawerHeaderIconButton for shared close/action chrome instead of copying drawer-local icon button classes.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "DrawerHeaderIconButton"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
],
"legacyReason": "Retired migration debt. Drawer header close and icon actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "upgrade-action-local-class-helper",
"category": "upgrade-action",
"summary": "Product surfaces must not use a class helper to make upgrade CTAs look like buttons; compose UpgradeButtonLink instead.",
"canonical": {
"path": "src/components/shared/UpgradeLink.tsx",
"export": "UpgradeButtonLink"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["getUpgradeActionButtonClass"],
"legacyReason": "Retired migration debt. Upgrade action button shape, routing, and link safety belong to UpgradeButtonLink.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/UpgradeLink.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "upgrade-action-local-button-shell",
"category": "upgrade-action",
"summary": "Product surfaces must not style UpgradeLink as a local button; use UpgradeButtonLink for button-styled upgrade actions and reserve UpgradeLink for inline text links.",
"canonical": {
"path": "src/components/shared/UpgradeLink.tsx",
"export": "UpgradeButtonLink"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"<UpgradeLink",
"min-h-10 sm:min-h-9 rounded-md border",
"text-xs font-medium"
],
"legacyReason": "Retired migration debt. Button-styled upgrade actions must use UpgradeButtonLink; inline copy links may continue using UpgradeLink.",
"allowedPaths": [],
"ignoredPaths": [
"src/components/Settings/AIRuntimeControlsSection.tsx",
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/UpgradeLink.test.tsx"
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/UpgradeLink.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "copy-value-neutral-local-button-shell",
"category": "copy-action",
"summary": "Copy-value controls must not copy the neutral bordered icon-button shell; use CopyValueButton with the neutral variant.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "CopyValueButton"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded border border-border bg-surface px-2 text-muted transition-colors hover:bg-surface-hover hover:text-base-content",
"fallback={<CopyIcon class=\"h-3.5 w-3.5\" />}"
],
"legacyReason": "Retired migration debt. Neutral copy-value icon buttons belong to CopyValueButton.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "copy-value-muted-local-button-shell",
"category": "copy-action",
"summary": "Copy-value controls must not copy the muted icon-button shell; use CopyValueButton with the ghost variant.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "CopyValueButton"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"inline-flex min-h-8 min-w-8 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content",
"fallback={<CopyIcon class=\"h-3.5 w-3.5\" />}"
],
"legacyReason": "Retired migration debt. Muted copy-value icon buttons belong to CopyValueButton.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "copy-value-accent-local-button-shell",
"category": "copy-action",
"summary": "Copy-value controls must not copy the blue suggested-value icon-button shell; use CopyValueButton with the accent variant.",
"canonical": {
"path": "src/components/shared/Button.tsx",
"export": "CopyValueButton"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"inline-flex min-h-7 min-w-7 shrink-0 items-center justify-center rounded text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950",
"fallback={<CopyIcon class=\"h-3.5 w-3.5\" />}"
],
"legacyReason": "Retired migration debt. Accent copy-value icon buttons belong to CopyValueButton.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "copyable-code-row-local-shell",
"category": "copy-action",
"summary": "Copyable code/value rows must not copy the local surface-alt font-mono row shell; use CopyableCodeRow.",
"canonical": {
"path": "src/components/shared/CopyableCodeRow.tsx",
"export": "CopyableCodeRow"
},
"scopes": ["src/components", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"flex items-start gap-2 rounded bg-surface-alt px-2 py-1.5",
"break-all font-mono text-xs text-base-content"
],
"legacyReason": "Retired migration debt. Copyable code rows belong to CopyableCodeRow so code styling and copy behavior stay canonical.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "inline-detail-local-field-grid-shell",
"category": "row-detail",
"summary": "Inline platform detail content must not recreate local DetailField grids; use DetailSectionTable or InlineDetailPanel.",
"canonical": {
"path": "src/components/shared/DetailSectionTable.tsx",
"export": "InlineDetailPanel"
},
"scopes": [
"src/components/Infrastructure",
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"allPatterns": [
"const DetailField",
"font-semibold uppercase tracking-wide text-muted",
"grid gap-3 sm:grid-cols-2 lg:grid-cols-3"
],
"legacyReason": "Retired migration debt. Inline detail field grids belong to DetailSectionTable / InlineDetailPanel so detail sections, value tones, truncation, and close actions stay canonical.",
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/DetailSectionTable.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "inline-detail-provider-named-primitive-import",
"category": "row-detail",
"summary": "Platform-neutral detail section tables must not import the retired provider-named TrueNAS detail primitive.",
"canonical": {
"path": "src/components/shared/DetailSectionTable.tsx",
"export": "DetailSectionTable"
},
"scopes": ["src/components", "src/features"],
"extensions": [".ts", ".tsx"],
"allPatterns": ["TrueNASDetailTable"],
"legacyReason": "Retired migration debt. Provider-specific names must not own platform-neutral detail tables; use DetailSectionTable / detailSectionModel.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/__tests__/DetailSectionTable.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-compact-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and hosted-admin surfaces must not copy compact xs action-button shells; use Button with the settingsActionXs or sm size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"rounded-md border border-border bg-surface px-3 py-2 text-xs font-medium text-base-content"
],
"legacyReason": "Retired migration debt. Compact settings and admin actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-secondary-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the standard secondary px-3 py-2 action-button shell; use Button or ButtonLink with the mdCompact size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"rounded-md border border-border bg-surface px-3 py-2 text-sm font-medium text-base-content"
],
"legacyReason": "Retired migration debt. Standard settings-sized secondary command buttons belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-outline-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the bordered px-3 py-2 action-button shell; use Button or ButtonLink with the outline variant and settingsAction size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"rounded-md border border-border px-3 py-2 text-sm font-medium text-base-content"
],
"legacyReason": "Retired migration debt. Standard bordered settings actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-primary-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the blue px-3 py-2 primary action-button shell; use Button with the primary variant and settingsAction size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white"],
"legacyReason": "Retired migration debt. Primary settings actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-success-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the emerald px-3 py-2 success action-button shell; use Button with the success variant and mdCompact or settingsAction size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["rounded-md bg-emerald-600 px-3 py-2 text-sm font-medium text-white"],
"legacyReason": "Retired migration debt. Positive settings actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-success-outline-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the emerald outlined px-3 py-2 success action-button shell; use Button with the successOutline variant and mdCompact or settingsAction size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"rounded-md border border-emerald-300 bg-white px-3 py-2 text-sm font-medium text-emerald-900"
],
"legacyReason": "Retired migration debt. Positive outlined settings actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-success-ghost-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the emerald ghost px-3 py-2 success action-button shell; use Button with the successGhost variant and mdCompact or settingsAction size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["rounded-md px-3 py-2 text-sm font-medium text-emerald-900"],
"legacyReason": "Retired migration debt. Positive ghost settings actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-large-reporting-action-local-shell",
"category": "action-button",
"summary": "Reporting and similar settings CTA rows must not copy the large full-width action-button shell; use Button with the lg size and contextual variant.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"flex w-full items-center justify-center gap-2 rounded-md px-6 py-3 font-semibold transition-all sm:w-auto"
],
"legacyReason": "Retired migration debt. Large settings/report actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-danger-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the rose px-3 py-2 destructive action-button shell; use Button with the danger variant and settingsAction size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": ["rounded-md bg-rose-600 px-3 py-2 text-sm font-medium text-white"],
"legacyReason": "Retired migration debt. Confirmed destructive settings actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-danger-outline-settings-action-local-shell",
"category": "action-button",
"summary": "Settings and infrastructure surfaces must not copy the rose outlined px-3 py-2 destructive action-button shell; use Button with the dangerOutline variant and settingsAction size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"rounded-md border border-rose-300 px-3 py-2 text-sm font-medium text-rose-700"
],
"legacyReason": "Retired migration debt. Unconfirmed destructive settings actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-outline-settings-row-action-local-shell",
"category": "action-button",
"summary": "Settings row and inline actions must not copy the compact bordered px-2.5 py-1 action shell; use Button or ButtonLink with the outline variant and xs size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"rounded-md border border-border px-2.5 py-1 text-xs font-medium text-base-content"
],
"legacyReason": "Retired migration debt. Compact settings row actions belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "button-outline-settings-dialog-close-local-shell",
"category": "action-button",
"summary": "Settings dialog close buttons must not copy the h-9 w-9 bordered icon shell; use Button with the outline variant and iconMd size.",
"canonical": {
"path": "src/components/shared/buttonModel.ts",
"export": "getButtonClass"
},
"scopes": ["src/components/Settings", "src/features", "src/pages"],
"extensions": [".tsx"],
"allPatterns": [
"h-9 w-9 items-center justify-center rounded-md border border-border text-base-content transition-colors hover:bg-surface-hover"
],
"legacyReason": "Retired migration debt. Settings dialog close buttons belong to the shared Button primitive family.",
"allowedPaths": [],
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/Button.test.tsx",
"scripts/shared-template-audit.mjs"
]
}
],
"requiredPatternGuards": [
{
"id": "platform-row-detail-toggle-shared-state",
"category": "row-disclosure",
"summary": "Platform rows using shared resource-detail expansion state must render the canonical disclosure toggle instead of relying on row-only click behavior or local chevrons.",
"canonical": {
"path": "src/features/platformPage/PlatformResourceDetailTableRow.tsx",
"export": "PlatformResourceDetailToggleButton"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["createPlatformResourceDetailState"],
"requiredPatterns": ["PlatformResourceDetailToggleButton"],
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-indicator-required",
"category": "status-indicator",
"summary": "Feature alert tables with severityBucket-driven platform rows must compose AlertSeverityBadge and AlertSeverityDot so new platform alert tables cannot invent local severity indicators.",
"canonical": {
"path": "src/components/shared/AlertSeverityBadge.tsx",
"export": "AlertSeverityBadge"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": [
"severityBucket",
"IncidentRow",
"getPlatformTableCellClassForKind('badge')"
],
"requiredPatterns": ["AlertSeverityBadge", "AlertSeverityDot"],
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/components/shared/AlertSeverityBadge.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-severity-filter-options-required",
"category": "filter-options",
"summary": "Feature alert tables with severityBucket-backed PlatformTableToolbar status filters must compose the shared platform alert severity filter-options primitive.",
"canonical": {
"path": "src/features/platformPage/platformAlertSeverityFilterOptions.tsx",
"export": "getPlatformAlertSeverityFilterOptions"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["severityBucket", "PlatformTableToolbar", "statusOptions"],
"requiredPatterns": ["getPlatformAlertSeverityFilterOptions"],
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/features/platformPage/__tests__/platformAlertSeverityFilterOptions.test.tsx",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-alert-detail-formatters-required",
"category": "detail-formatting",
"summary": "Feature alert tables with incident-row detail code and started-at fields must compose the shared platform alert detail formatter primitive.",
"canonical": {
"path": "src/utils/alertDetailPresentation.ts",
"export": "formatPlatformAlertCode"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["IncidentRow", "incident.code", "incident.startedAt"],
"requiredPatterns": [
"formatPlatformAlertCode",
"formatPlatformAlertStartedAt",
"formatPlatformAlertDetailDateTime"
],
"allowedPaths": [],
"ignoredPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"src/utils/__tests__/alertDetailPresentation.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-row-detail-toggle-inline-detail",
"category": "row-disclosure",
"summary": "Platform tables that render page-local inline detail rows must compose the canonical disclosure toggle in the owning row.",
"canonical": {
"path": "src/features/platformPage/PlatformResourceDetailTableRow.tsx",
"export": "PlatformResourceDetailToggleButton"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["data-inline", "aria-expanded"],
"requiredPatterns": ["PlatformResourceDetailToggleButton"],
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "inline-detail-table-row-shared-shell",
"category": "row-detail",
"summary": "Files that render inline/detail table rows in platform, workload, or infrastructure surfaces must use InlineDetailTableRow for the shared row/cell/content shell.",
"canonical": {
"path": "src/components/shared/InlineDetailTableRow.tsx",
"export": "InlineDetailTableRow"
},
"scopes": [
"src/components/Infrastructure",
"src/components/Workloads",
"src/features/docker",
"src/features/kubernetes",
"src/features/platformPage",
"src/features/proxmox",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["data-inline", "TableRow"],
"requiredPatterns": ["InlineDetailTableRow"],
"allowedPaths": [],
"ignoredPaths": [
"src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx"
],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "platform-error-state-required",
"category": "platform-error-state",
"summary": "Platform feature files that surface load-failure copy must compose PlatformErrorState for the canonical retry shell.",
"canonical": {
"path": "src/features/platformPage/sharedPlatformPage.tsx",
"export": "PlatformErrorState"
},
"scopes": [
"src/features/docker",
"src/features/kubernetes",
"src/features/proxmox",
"src/features/standalone",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["title=\"Could not load"],
"requiredPatterns": ["PlatformErrorState"],
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
},
{
"id": "named-detail-table-row-shared-shell",
"category": "row-detail",
"summary": "Named platform detail rows such as Docker network detail rows must use InlineDetailTableRow even when their row attribute is not data-inline-*.",
"canonical": {
"path": "src/components/shared/InlineDetailTableRow.tsx",
"export": "InlineDetailTableRow"
},
"scopes": [
"src/components/Infrastructure",
"src/components/Workloads",
"src/features/docker",
"src/features/kubernetes",
"src/features/platformPage",
"src/features/proxmox",
"src/features/truenas",
"src/features/vmware"
],
"extensions": [".tsx"],
"triggerPatterns": ["detail-row", "TableRow"],
"requiredPatterns": ["InlineDetailTableRow"],
"allowedPaths": [],
"proof": [
"src/components/shared/SharedPrimitives.guardrails.test.ts",
"scripts/shared-template-audit.mjs"
]
}
]
}