{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "validation-panel",
  "title": "Validation panel",
  "description": "Lists policy issues with severities: structural errors that should block activation and best-practice warnings (segregation of duties, single approver on high value, unassigned seats). Click an issue to focus the step on the canvas.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "https://approvals-ui.vercel.app/r/approvals-core.json"
  ],
  "files": [
    {
      "path": "components/approvals-ui/validation-panel.tsx",
      "content": "\"use client\";\n\nimport { CircleCheck, OctagonX, TriangleAlert } from \"lucide-react\";\n\nimport type { PolicyIssue } from \"@/lib/approvals-ui/validate\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ValidationPanelProps = {\n  issues: PolicyIssue[];\n  /** Called with a step id when an issue is clicked: wire it to the canvas focus. */\n  onFocusStep?: (stepId: string) => void;\n  className?: string;\n};\n\nexport const ValidationPanel = ({ issues, onFocusStep, className }: ValidationPanelProps) => {\n  if (issues.length === 0) {\n    return (\n      <div className={cn(\"flex items-center gap-2 rounded-lg border p-3\", className)}>\n        <CircleCheck className=\"size-4 shrink-0 text-emerald-600 dark:text-emerald-400\" />\n        <p className=\"text-muted-foreground text-xs\">No issues. The policy is safe to activate.</p>\n      </div>\n    );\n  }\n\n  const errors = issues.filter((i) => i.severity === \"error\").length;\n  const warnings = issues.length - errors;\n\n  return (\n    <div className={cn(\"space-y-2\", className)}>\n      <div className=\"flex items-center gap-1.5\">\n        {errors > 0 && (\n          <Badge variant=\"destructive\" className=\"text-[10px]\">\n            {errors} error{errors === 1 ? \"\" : \"s\"}\n          </Badge>\n        )}\n        {warnings > 0 && (\n          <Badge\n            variant=\"secondary\"\n            className=\"border-amber-500/40 bg-amber-500/10 text-[10px] text-amber-700 dark:text-amber-400\"\n          >\n            {warnings} warning{warnings === 1 ? \"\" : \"s\"}\n          </Badge>\n        )}\n      </div>\n      <ul className=\"space-y-1\">\n        {issues.map((issue) => {\n          const firstStepId = issue.stepIds[0];\n          const clickable = onFocusStep && firstStepId !== undefined;\n          // validatePolicy dedupes on code + sorted stepIds and each rule emits\n          // its stepIds in a deterministic order, so code + stepIds is a stable\n          // unique identity for the issue (no array index needed).\n          const key = `${issue.code}::${issue.stepIds.join(\",\")}`;\n          return (\n            <li key={key}>\n              <button\n                type=\"button\"\n                disabled={!clickable}\n                onClick={() => clickable && onFocusStep(firstStepId)}\n                className={cn(\n                  \"flex w-full items-start gap-2 rounded-md p-2 text-left\",\n                  clickable && \"hover:bg-muted/60 cursor-pointer transition-colors\"\n                )}\n              >\n                {issue.severity === \"error\" ? (\n                  <OctagonX className=\"text-destructive mt-0.5 size-3.5 shrink-0\" />\n                ) : (\n                  <TriangleAlert className=\"mt-0.5 size-3.5 shrink-0 text-amber-500\" />\n                )}\n                <span className=\"min-w-0 space-y-0.5\">\n                  <span className=\"block text-xs leading-snug\">{issue.message}</span>\n                  <span className=\"text-muted-foreground block font-mono text-[10px]\">\n                    {issue.code}\n                  </span>\n                </span>\n              </button>\n            </li>\n          );\n        })}\n      </ul>\n    </div>\n  );\n};\n",
      "type": "registry:component",
      "target": "components/approvals-ui/validation-panel.tsx"
    }
  ],
  "type": "registry:component"
}