{ "version": 1, "rules": [ { "id": "operator-first-drawer-attention", "category": "drawer-information-hierarchy", "summary": "Object drawers surface active problem messages before additive operator context through one shared compact attention section.", "canonical": { "path": "src/components/shared/DrawerAttentionSection.tsx", "export": "DrawerAttentionSection" }, "requiredConsumers": [ { "path": "src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx" }, { "path": "src/components/Workloads/GuestDrawerOverview.tsx" }, { "path": "src/components/Workloads/NodeDrawerOverview.tsx" }, { "path": "src/features/docker/DockerHostDrawerOverview.tsx" } ], "forbiddenPatterns": [ { "path": "src/components/Infrastructure/ResourceDetailDrawer.tsx", "patterns": [ "Ask Pulse Assistant about", "Copy Pulse context for", "openAssistantForResource", "copyAgentContext" ] }, { "path": "src/components/Workloads/GuestDrawer.tsx", "patterns": [ "Ask Pulse Assistant about", "Copy Pulse context for", "openAssistantForGuest", "copyAgentContext" ] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/components/Workloads/GuestDrawer.test.tsx", "src/components/Workloads/NodeDrawer.test.tsx" ] }, { "id": "operator-first-technical-section", "category": "drawer-information-hierarchy", "summary": "Object drawers keep curated deeper inventory visible through the same compact section rows as Overview; only genuinely large or interactive provider-support material stays behind a disclosure.", "canonical": { "path": "src/components/shared/TechnicalDetailsDisclosure.tsx", "export": "TechnicalDetailsSection" }, "requiredConsumers": [ { "path": "src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx" }, { "path": "src/components/Workloads/GuestDrawerOverview.tsx" }, { "path": "src/components/Workloads/NodeDrawerOverview.tsx" }, { "path": "src/features/docker/DockerHostDrawerOverview.tsx" } ], "forbiddenPatterns": [ { "path": "src/components/Workloads/GuestDrawerOverview.tsx", "patterns": ["const DetailCard"] }, { "path": "src/components/Workloads/NodeDrawerOverview.tsx", "patterns": ["const DetailCard"] }, { "path": "src/features/docker/DockerHostDrawerOverview.tsx", "patterns": ["const DetailCard"] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/components/Workloads/GuestDrawer.test.tsx", "src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx" ] }, { "id": "runtime-web-interface-inline-link", "category": "resource-launch", "summary": "Runtime resource rows keep names available for row selection and expose saved or inferred web-interface URLs through the shared adjacent-link template, not a page-local link or separate web column.", "canonical": { "path": "src/components/shared/WebInterfaceLink.tsx", "export": "ResourceNameWithWebInterfaceLink" }, "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" }, { "path": "src/components/Alerts/AlertResourceGroupHeader.tsx" }, { "path": "src/components/Alerts/AlertResourceTableRow.tsx" }, { "path": "src/features/docker/DockerNativeTableShared.tsx" }, { "path": "src/features/docker/DockerContainersTable.tsx" }, { "path": "src/features/kubernetes/KubernetesClustersTable.tsx" }, { "path": "src/features/kubernetes/KubernetesNodesTable.tsx" }, { "path": "src/features/kubernetes/KubernetesPodsTable.tsx" }, { "path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx" }, { "path": "src/features/kubernetes/KubernetesServicesTable.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'"] }, { "path": "src/components/Alerts/AlertResourceGroupHeader.tsx", "patterns": ["target=\"_blank\"", "rel=\"noopener noreferrer\""] }, { "path": "src/components/Alerts/AlertResourceTableRow.tsx", "patterns": ["target=\"_blank\"", "rel=\"noopener noreferrer\""] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/components/shared/WebInterfaceLink.test.tsx", "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", "src/components/Alerts/ResourceTable.test.tsx" ] }, { "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/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": "toast-notification-shell", "category": "notification", "summary": "Toast notification status icons and dismiss actions must compose library icons and ActionIconButton so every page inherits shared notification chrome instead of raw SVG glyphs or local close-button classes.", "canonical": { "path": "src/components/Toast/Toast.tsx", "export": "Toast" }, "requiredConsumers": [{ "path": "src/components/Toast/Toast.tsx" }], "forbiddenPatterns": [ { "path": "src/components/Toast/Toast.tsx", "patterns": [ "= 100 ? 0 : value >= 10 ? 1 : 2)"] }, { "path": "src/features/kubernetes/KubernetesStorageTable.tsx", "patterns": ["formatBytes("] }, { "path": "src/features/proxmox/ProxmoxBackupServersTable.tsx", "patterns": ["formatBytes("] }, { "path": "src/features/proxmox/ProxmoxCephTable.tsx", "patterns": ["formatBytes("] }, { "path": "src/features/proxmox/ProxmoxCoverageTable.tsx", "patterns": ["formatBytes("] }, { "path": "src/features/proxmox/ProxmoxRecoverableTable.tsx", "patterns": ["formatBytes("] }, { "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-date-time-value", "category": "platform-table-text-value", "summary": "Platform tables must use the shared compact date-time value primitive for timestamp cells, tabular styling, and canonical empty-cell markers instead of recreating local compact toLocaleString helpers.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableDateTimeValue" }, "requiredConsumers": [{ "path": "src/features/truenas/TrueNASProtectionTable.tsx" }], "forbiddenPatterns": [ { "path": "src/features/truenas/TrueNASProtectionTable.tsx", "patterns": ["const formatPointTime", "formatPointTime(point)"] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/features/platformPage/__tests__/sharedPlatformPage.test.ts", "scripts/shared-template-audit.mjs" ] }, { "id": "platform-table-relative-time-value", "category": "platform-table-text-value", "summary": "Platform tables must use the shared relative-time value primitive for dense timestamp-age cells, tabular styling, compact labels, and canonical empty-cell markers instead of calling formatRelativeTime directly.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableRelativeTimeValue" }, "requiredConsumers": [ { "path": "src/features/docker/DockerVolumesTable.tsx" }, { "path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx" }, { "path": "src/features/kubernetes/KubernetesEventsTable.tsx" }, { "path": "src/features/proxmox/proxmoxBackupsTableShared.tsx" }, { "path": "src/features/proxmox/ProxmoxReplicationTable.tsx" }, { "path": "src/features/standalone/AvailabilityChecksTable.tsx" }, { "path": "src/features/standalone/AgentsMachinesTable.tsx" }, { "path": "src/features/vmware/VsphereActivityTable.tsx" } ], "forbiddenPatterns": [ { "path": "src/features/docker/DockerVolumesTable.tsx", "patterns": ["formatRelativeTime(parsed)"] }, { "path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx", "patterns": ["formatRelativeTime(createdAt, { compact: true, emptyText: '—' })"] }, { "path": "src/features/kubernetes/KubernetesEventsTable.tsx", "patterns": [ "formatRelativeTime(observedTimestamp(resource), { compact: true, emptyText: '—' })" ] }, { "path": "src/features/proxmox/proxmoxBackupsTableShared.tsx", "patterns": ["formatRelativeTime(props.artifact.createdAt, { compact: true })"] }, { "path": "src/features/proxmox/ProxmoxReplicationTable.tsx", "patterns": [ "formatRelativeTime(job.lastSyncUnix * 1000, { compact: true })", "formatRelativeTime(job.lastSyncTime, { compact: true })" ] }, { "path": "src/features/standalone/AgentsMachinesTable.tsx", "patterns": ["const formatLastSeen", "Math.floor((Date.now() - timestampMillis) / 1000)"] }, { "path": "src/features/standalone/AvailabilityChecksTable.tsx", "patterns": ["formatRelativeTime(availability?.lastChecked", "const formatChecked"] }, { "path": "src/features/vmware/VsphereActivityTable.tsx", "patterns": [ "const formatActivityDate", "formatActivityDate(activity.occurredAt || activity.observedAt)" ] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/features/platformPage/__tests__/sharedPlatformPage.test.ts", "scripts/shared-template-audit.mjs" ] }, { "id": "platform-table-duration-value", "category": "platform-table-text-value", "summary": "Platform tables must use the shared duration value primitive for elapsed duration and polling interval cells, tabular styling, fallback labels, and canonical empty-cell markers instead of declaring local seconds/minutes helpers.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableDurationValue" }, "requiredConsumers": [ { "path": "src/features/proxmox/ProxmoxReplicationTable.tsx" }, { "path": "src/features/standalone/AvailabilityChecksTable.tsx" } ], "forbiddenPatterns": [ { "path": "src/features/proxmox/ProxmoxReplicationTable.tsx", "patterns": ["function formatDuration", "seconds < 3600"] }, { "path": "src/features/standalone/AvailabilityChecksTable.tsx", "patterns": ["const formatInterval", "toFixed(1)}m"] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/features/platformPage/__tests__/sharedPlatformPage.test.ts", "scripts/shared-template-audit.mjs" ] }, { "id": "platform-table-weighted-column-width-style", "category": "platform-table-layout", "summary": "Responsive platform table models must use the shared weighted column-width helper for visible-column percentage normalization instead of declaring local percentage-format helpers.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "getPlatformTableWeightedColumnWidthStyle" }, "requiredConsumers": [ { "path": "src/features/docker/dockerContainerTableModel.ts" }, { "path": "src/features/proxmox/proxmoxHostTableModel.ts" } ], "forbiddenPatterns": [ { "path": "src/features/docker/dockerContainerTableModel.ts", "patterns": ["const formatPercentage", "value.toFixed(4)"] }, { "path": "src/features/proxmox/proxmoxHostTableModel.ts", "patterns": ["const formatPercentage", "value.toFixed(4)"] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/features/platformPage/__tests__/sharedPlatformPage.test.ts", "scripts/shared-template-audit.mjs" ] }, { "id": "platform-table-integer-value-format", "category": "platform-table-text-value", "summary": "Platform tables and drawers must use the shared integer formatter with PlatformTableNumberValue for locale-formatted count cells instead of recreating local toLocaleString or Math.round helpers.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "formatPlatformTableIntegerValue" }, "requiredConsumers": [ { "path": "src/components/Kubernetes/K8sNamespacesDrawer.tsx" }, { "path": "src/features/proxmox/ProxmoxBackupServersTable.tsx" }, { "path": "src/features/proxmox/ProxmoxCephClusterDrawer.tsx" }, { "path": "src/features/proxmox/ProxmoxMailGatewayDrawer.tsx" }, { "path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx" } ], "forbiddenPatterns": [ { "path": "src/components/Kubernetes/K8sNamespacesDrawer.tsx", "patterns": ["const formatInteger", "Math.round(n).toLocaleString()"] }, { "path": "src/features/proxmox/ProxmoxBackupServersTable.tsx", "patterns": ["row.backupCount.toLocaleString()"] }, { "path": "src/features/proxmox/ProxmoxCephClusterDrawer.tsx", "patterns": ["pool.objects.toLocaleString()"] }, { "path": "src/features/proxmox/ProxmoxMailGatewayDrawer.tsx", "patterns": ["function formatNumber", "Math.round(value).toLocaleString()"] }, { "path": "src/features/proxmox/ProxmoxMailGatewayTable.tsx", "patterns": ["const formatLocaleCount", "value.toLocaleString()"] } ], "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' ? {value} : —" ] }, { "path": "src/features/docker/DockerServicesTable.tsx", "patterns": ["const replicaCount", "{value ?? 0}"] }, { "path": "src/components/Docker/SwarmServicesDrawer.tsx", "patterns": [ "{desired()}", "{running()}" ] }, { "path": "src/features/kubernetes/KubernetesAutoscalingTable.tsx", "patterns": [ "const numberValue", "typeof value === 'number' ? {value} : —" ] }, { "path": "src/features/kubernetes/KubernetesControllersTable.tsx", "patterns": [ "const numberValue", "typeof value === 'number' ? {value} : —" ] }, { "path": "src/features/kubernetes/KubernetesDeploymentsTable.tsx", "patterns": ["const replicaCount", "{value ?? 0}"] }, { "path": "src/features/kubernetes/KubernetesEventsTable.tsx", "patterns": [ "const numberValue", "typeof value === 'number' ? {value} : —" ] }, { "path": "src/features/kubernetes/KubernetesPodsTable.tsx", "patterns": [ "const numericValue", "typeof value === 'number' ? {value} : —" ] }, { "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", "/{count.total}"] } ], "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-label", "category": "platform-table-text-value", "summary": "Platform tables must use the shared count-ratio formatter for ready/total string labels instead of recreating local ready/total template literals.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "formatPlatformTableCountRatioValue" }, "requiredConsumers": [{ "path": "src/features/kubernetes/KubernetesNetworkingTable.tsx" }], "forbiddenPatterns": [ { "path": "src/features/kubernetes/KubernetesNetworkingTable.tsx", "patterns": [ "`${readyValue}/${totalValue} ready`", "`${ready ?? 0}/${total ?? ready ?? 0} ready`" ] } ], "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 and drawers must use the shared percent-value primitive/helper for one-decimal percent cells, overlay labels, tabular styling, ratio normalization, clamping, and canonical empty-cell markers instead of recreating local formatPercent helpers.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTablePercentValue" }, "requiredConsumers": [ { "path": "src/features/proxmox/ProxmoxBackupServersTable.tsx" }, { "path": "src/features/proxmox/ProxmoxCephClusterDrawer.tsx" }, { "path": "src/features/proxmox/ProxmoxCephTable.tsx" }, { "path": "src/features/proxmox/ProxmoxMailGatewayDrawer.tsx" }, { "path": "src/features/proxmox/ProxmoxNodesTable.tsx" }, { "path": "src/features/truenas/TrueNASSystemsTable.tsx" } ], "forbiddenPatterns": [ { "path": "src/features/proxmox/ProxmoxBackupServersTable.tsx", "patterns": [ "Math.round(row.cpuPercent ?? 0)}%", "Math.round(row.memoryPercent ?? 0)}%", "Math.round(pct() ?? 0)}%" ] }, { "path": "src/features/proxmox/ProxmoxCephClusterDrawer.tsx", "patterns": ["clamped.toFixed(1)}%"] }, { "path": "src/features/proxmox/ProxmoxCephTable.tsx", "patterns": ["pct.toFixed(1)}%"] }, { "path": "src/features/proxmox/ProxmoxMailGatewayDrawer.tsx", "patterns": ["share.toFixed(1)}%"] }, { "path": "src/features/proxmox/ProxmoxNodesTable.tsx", "patterns": ["const formatPercentLabel", "`${Math.round(Math.max(0, normalized))}%`"] }, { "path": "src/features/truenas/TrueNASSystemsTable.tsx", "patterns": [ "const formatPercent", "{percent.toFixed(1)}%" ] } ], "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", "{celsius.toFixed(1)}°C" ] }, { "path": "src/features/truenas/TrueNASStorageTopologyTable.tsx", "patterns": ["const temperatureLabel", "${Math.round(value)}C"] }, { "path": "src/features/truenas/TrueNASSystemsTable.tsx", "patterns": [ "const formatTemperature", "{celsius.toFixed(1)}°C" ] } ], "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-table-finite-metric-normalization", "category": "platform-table-metric", "summary": "Platform table models must use the shared finite-metric normalization helper instead of carrying model-local Number.isFinite wrappers.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "getPlatformTableFiniteMetric" }, "requiredConsumers": [{ "path": "src/features/standalone/agentMachineTableModel.ts" }], "forbiddenPatterns": [ { "path": "src/features/standalone/agentMachineTableModel.ts", "patterns": ["const finiteMetric"] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.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'", " 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", "" ] }, { "path": "src/features/proxmox/ProxmoxBackupsTable.tsx", "patterns": [ "const viewButtonClass", "inline-flex items-center gap-1 rounded-md border border-border bg-surface p-1", "inline-flex min-h-8 items-center gap-1.5 rounded-xs px-3 text-xs font-medium transition-colors" ] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/components/shared/FilterButtonGroup.test.tsx", "scripts/shared-template-audit.mjs" ] }, { "id": "selectable-pill-button-shell", "category": "selectable-pill-button", "summary": "Pressed/unpressed rounded pill selectors must compose SelectablePillButton so active tone, disabled treatment, focus ring, and aria-pressed semantics stay shared instead of being recreated inside settings surfaces.", "canonical": { "path": "src/components/shared/SelectablePillButton.tsx", "export": "SelectablePillButton" }, "requiredConsumers": [{ "path": "src/components/Settings/APITokenManager.tsx" }], "forbiddenPatterns": [ { "path": "src/components/Settings/APITokenManager.tsx", "patterns": [ "inline-flex min-h-10 sm:min-h-10 items-center rounded-full border px-3 py-2 text-sm font-semibold transition", "min-h-10 sm:min-h-10 rounded-full border px-3 py-2 text-sm font-semibold transition", "border-blue-500 bg-blue-600 text-white shadow-xs", "hover:border-blue-400 hover:text-blue-600 dark:hover:border-blue-400 dark:hover:text-blue-200" ] } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/components/shared/SelectablePillButton.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" ] }, { "id": "platform-estate-inline-counts", "category": "platform-overview", "summary": "Large-estate totals annotate the existing canonical type/status controls and table headers instead of creating a competing overview panel; the shared View preference owns their visibility.", "canonical": { "path": "src/components/Workloads/WorkloadsFilter.tsx", "export": "WorkloadsFilter" }, "requiredConsumers": [ { "path": "src/features/proxmox/ProxmoxPageSurface.tsx" }, { "path": "src/features/vmware/VmwarePageSurface.tsx" } ], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/components/Workloads/__tests__/WorkloadsFilter.test.tsx", "src/features/platformPage/__tests__/platformEstateOverviewModel.test.ts", "src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts", "scripts/shared-template-audit.mjs" ] } ], "patternGuards": [ { "id": "alert-resource-action-local-svg-button-shell", "category": "action-button", "summary": "Alert resource threshold action controls must not recreate icon-only buttons with inline SVGs locally; compose ActionIconButton and lucide icons for row, mobile, and global actions.", "canonical": { "path": "src/components/shared/Button.tsx", "export": "ActionIconButton" }, "scopes": ["src/components/Alerts"], "extensions": [".tsx"], "allPatterns": ["= 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/docker", "src/features/kubernetes", "src/features/proxmox", "src/features/truenas" ], "extensions": [".tsx"], "pathIncludes": ["Table"], "pathExcludes": ["__tests__"], "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-format-uptime-import", "category": "platform-table-text-value", "summary": "Platform table files must not call the generic formatUptime utility directly for uptime cells.", "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", "src/features/vmware" ], "extensions": [".tsx"], "pathIncludes": ["Table"], "pathExcludes": ["__tests__"], "allPatterns": ["formatUptime("], "legacyReason": "Retired migration debt. Dense platform table uptime cells must compose formatPlatformTableUptimeValue so compact/full labels 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": ["{value}", "—"], "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-integer-format-helper", "category": "platform-table-text-value", "summary": "Platform tables and drawers must not recreate local integer format helpers for count labels.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "formatPlatformTableIntegerValue" }, "scopes": ["src/components/Kubernetes", "src/features/proxmox"], "extensions": [".tsx"], "allPatterns": ["Math.round(", ".toLocaleString()"], "legacyReason": "Retired migration debt. Dense platform integer count labels must use formatPlatformTableIntegerValue so rounding, locale 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-integer-count-tolocalestring", "category": "platform-table-text-value", "summary": "Platform tables and drawers must not call toLocaleString directly for count cells.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "formatPlatformTableIntegerValue" }, "scopes": ["src/components/Kubernetes", "src/features/proxmox"], "extensions": [".tsx"], "allPatterns": ["toLocaleString()"], "legacyReason": "Retired migration debt. Dense platform integer count cells must use formatPlatformTableIntegerValue and PlatformTableNumberValue instead of local count formatting.", "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", "{value ?? 0}"], "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", "/{count.total}"], "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-ready-count-ratio-label", "category": "platform-table-text-value", "summary": "Kubernetes networking tables must not recreate ready/total endpoint count labels with local template strings.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "formatPlatformTableCountRatioValue" }, "scopes": ["src/features/kubernetes/KubernetesNetworkingTable.tsx"], "extensions": [".tsx"], "allPatterns": ["readyEndpointCount", "/${", " ready`"], "legacyReason": "Retired migration debt. Ready/total endpoint labels must compose formatPlatformTableCountRatioValue so zero-default, suffix, and empty-label behavior stay shared with count-ratio cells.", "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-date-time-helper", "category": "platform-table-text-value", "summary": "TrueNAS protection tables must not recreate compact completed-time helpers for timestamp cells.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableDateTimeValue" }, "scopes": ["src/features/truenas/TrueNASProtectionTable.tsx"], "extensions": [".tsx"], "allPatterns": ["const formatPointTime", "toLocaleString(undefined, {", "minute: '2-digit'"], "legacyReason": "Retired migration debt. Compact platform timestamp cells must compose PlatformTableDateTimeValue so date parsing, empty markers, and tabular styling 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-vsphere-activity-date-helper", "category": "platform-table-text-value", "summary": "vSphere activity tables must not recreate compact activity-date helpers for timestamp cells.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableRelativeTimeValue" }, "scopes": ["src/features/vmware/VsphereActivityTable.tsx"], "extensions": [".tsx"], "allPatterns": [ "const formatActivityDate", "toLocaleString(undefined, {", "minute: '2-digit'" ], "legacyReason": "Retired migration debt. vSphere activity reads as an age through PlatformTableRelativeTimeValue, with the exact time on hover and in the drawer, so date parsing, empty markers, and tabular styling 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-relative-time-helper", "category": "platform-table-text-value", "summary": "Platform tables must not call formatRelativeTime directly for dense timestamp-age cells.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableRelativeTimeValue" }, "scopes": [ "src/features/docker", "src/features/kubernetes", "src/features/proxmox", "src/features/standalone" ], "extensions": [".tsx"], "pathIncludes": ["Table"], "pathExcludes": ["__tests__"], "allPatterns": ["formatRelativeTime("], "legacyReason": "Retired migration debt. Platform table relative timestamp cells must compose PlatformTableRelativeTimeValue so compact labels, empty markers, and tabular styling 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-duration-helper", "category": "platform-table-text-value", "summary": "Platform tables must not recreate local elapsed-duration helpers for seconds/minutes duration cells.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableDurationValue" }, "scopes": ["src/features/proxmox/ProxmoxReplicationTable.tsx"], "extensions": [".tsx"], "allPatterns": ["function formatDuration", "seconds < 3600", "return `${h}h ${m}m`"], "legacyReason": "Retired migration debt. Platform duration cells must compose PlatformTableDurationValue so elapsed-duration labels, fallback text, empty markers, and tabular styling 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-interval-helper", "category": "platform-table-text-value", "summary": "Platform tables must not recreate local interval helpers for polling interval cells.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "PlatformTableDurationValue" }, "scopes": ["src/features/standalone/AvailabilityChecksTable.tsx"], "extensions": [".tsx"], "allPatterns": ["const formatInterval", "pollIntervalSeconds", "toFixed(1)}m"], "legacyReason": "Retired migration debt. Platform interval cells must compose PlatformTableDurationValue so seconds/minutes labels, empty markers, and tabular styling 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-weighted-width-helper", "category": "platform-table-layout", "summary": "Responsive platform table models must not recreate local weighted-width percentage helpers.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "getPlatformTableWeightedColumnWidthStyle" }, "scopes": [ "src/features/docker/dockerContainerTableModel.ts", "src/features/proxmox/proxmoxHostTableModel.ts" ], "extensions": [".ts"], "allPatterns": [ "const formatPercentage", "value.toFixed(4)", "return { width: formatPercentage(width) }" ], "legacyReason": "Retired migration debt. Responsive platform table width normalization must use getPlatformTableWeightedColumnWidthStyle so percentage rounding and zero-width fallback 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", "{percent.toFixed(1)}%" ], "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-proxmox-percent-tofixed-label", "category": "platform-table-text-value", "summary": "Proxmox platform tables and drawers must not recreate one-decimal percent labels with toFixed.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "formatPlatformTablePercentValue" }, "scopes": ["src/features/proxmox"], "extensions": [".tsx"], "allPatterns": ["toFixed(1)}%"], "legacyReason": "Retired migration debt. Proxmox platform percent labels must compose formatPlatformTablePercentValue or PlatformTablePercentValue so formatting stays 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-proxmox-percent-label-helper", "category": "platform-table-text-value", "summary": "Proxmox platform tables must not recreate local percent label helpers for metric cells.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "formatPlatformTablePercentValue" }, "scopes": ["src/features/proxmox"], "extensions": [".tsx"], "allPatterns": ["formatPercentLabel"], "legacyReason": "Retired migration debt. Proxmox platform percent metric labels must compose formatPlatformTablePercentValue so ratio normalization, clamping, 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", "{celsius.toFixed(1)}°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-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": [ "class=\"flex justify-center\"", "class=\"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-table-model-local-finite-metric-helper", "category": "platform-table-metric", "summary": "Platform table models must not recreate local finiteMetric helpers for metric normalization.", "canonical": { "path": "src/features/platformPage/sharedPlatformPage.tsx", "export": "getPlatformTableFiniteMetric" }, "scopes": ["src/features/standalone/agentMachineTableModel.ts"], "extensions": [".ts"], "allPatterns": ["const finiteMetric"], "legacyReason": "Retired migration debt. Platform table model-side metric normalization must use getPlatformTableFiniteMetric so table presentation validity remains shared.", "allowedPaths": [], "ignoredPaths": [], "proof": [ "src/components/shared/SharedPrimitives.guardrails.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": "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-sm"], "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-sm"], "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-sm"], "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-xs 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"], "pathExcludes": [".test.", "__tests__"], "allPatterns": ["}" ], "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={}" ], "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-sm text-blue-700 transition-colors hover:bg-blue-100 dark:text-blue-200 dark:hover:bg-blue-950/25", "fallback={}" ], "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": "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": "resource-detail-local-provider-row-model", "category": "row-detail", "summary": "Provider resource detail drawer models must not define provider-local row and section primitives.", "canonical": { "path": "src/components/shared/detailSectionModel.ts", "export": "DetailSection" }, "scopes": ["src/components/Infrastructure/resourceDetailDrawerVmwareModel.ts"], "extensions": [".ts"], "allPatterns": ["ResourceDetailDrawerVMwareRow", "ResourceDetailDrawerVMwareSection"], "legacyReason": "Retired migration debt. Provider detail drawers own fields and labels, while row and section shape belongs to DetailSection / DetailRow in detailSectionModel.", "allowedPaths": [], "ignoredPaths": [], "proof": [ "src/components/shared/SharedPrimitives.guardrails.test.ts", "src/components/Infrastructure/__tests__/resourceDetailDrawerVmwareModel.test.ts", "scripts/shared-template-audit.mjs" ] }, { "id": "resource-detail-local-vmware-detail-shell", "category": "row-detail", "summary": "The resource detail drawer must not render VMware detail sections with a local vSphere card/table shell.", "canonical": { "path": "src/components/shared/DetailSectionTable.tsx", "export": "DetailSectionTable" }, "scopes": ["src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx"], "extensions": [".tsx"], "allPatterns": ["vmwareRowToneClass", "