{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scope-chat",
  "title": "Scope Chat",
  "description": "AI scoping chat UI. Wire CONTENT.endpoint (Host API).",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@atroui/utils",
    "@atroui/project-brief"
  ],
  "files": [
    {
      "path": "registry/default/blocks/scope-chat.tsx",
      "content": "\"use client\"\n\nimport { Bot, Loader2, Send, User } from \"lucide-react\"\nimport Link from \"next/link\"\nimport { useState, type FormEvent } from \"react\"\n\nimport {\n  briefFromScopeMessage,\n  buildOgHref,\n} from \"@/lib/project-brief\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Scope chat UI shell. Wire CONTENT.endpoint to your AI host API.\n * After a user message, \"Draft OG card\" prefills @atroui/og-workspace via ProjectBrief.\n */\nconst CONTENT = {\n  endpoint: \"/api/scope\",\n  placeholder: \"Describe the product you want to ship…\",\n  emptyHint: \"Ask for a fixed-scope plan. Host API required for replies.\",\n  /** Host route for OG workspace (query prefills Quick mode). */\n  ogPath: \"/og\",\n  maxMessages: 40,\n}\n\ntype Msg = { role: \"user\" | \"assistant\"; content: string }\n\nexport function ScopeChat() {\n  const [messages, setMessages] = useState<Msg[]>([])\n  const [input, setInput] = useState(\"\")\n  const [loading, setLoading] = useState(false)\n  const [error, setError] = useState<string | null>(null)\n\n  async function onSubmit(e: FormEvent) {\n    e.preventDefault()\n    const text = input.trim()\n    if (!text || loading) return\n    setInput(\"\")\n    setError(null)\n    const next: Msg[] = [...messages, { role: \"user\", content: text }].slice(\n      -CONTENT.maxMessages\n    )\n    setMessages(next)\n    setLoading(true)\n    try {\n      const res = await fetch(CONTENT.endpoint, {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: JSON.stringify({ messages: next }),\n      })\n      if (!res.ok) throw new Error(`Request failed (${res.status})`)\n      const data = (await res.json()) as { reply?: string }\n      setMessages((m) =>\n        [\n          ...m,\n          {\n            role: \"assistant\" as const,\n            content: data.reply ?? \"No reply from host API.\",\n          },\n        ].slice(-CONTENT.maxMessages)\n      )\n    } catch (err) {\n      setError(err instanceof Error ? err.message : \"Request failed\")\n    } finally {\n      setLoading(false)\n    }\n  }\n\n  return (\n    <div className=\"flex min-h-[420px] flex-col overflow-hidden rounded-2xl border border-border-subtle bg-card/40\">\n      <div className=\"border-b border-border-subtle px-4 py-3\">\n        <p className=\"ms-stamp\">Scope chat</p>\n        <p className=\"mt-1 text-xs text-muted-foreground\">{CONTENT.emptyHint}</p>\n      </div>\n      <div className=\"flex-1 space-y-3 overflow-y-auto p-4\">\n        {messages.length === 0 ? (\n          <p className=\"text-sm text-muted-foreground\">{CONTENT.placeholder}</p>\n        ) : (\n          messages.map((m, i) => (\n            <div\n              key={`${m.role}-${i}`}\n              className={cn(\n                \"flex gap-2 text-sm\",\n                m.role === \"user\" ? \"justify-end\" : \"justify-start\"\n              )}\n            >\n              {m.role === \"assistant\" ? (\n                <Bot className=\"mt-0.5 size-4 shrink-0 text-brand\" />\n              ) : null}\n              <p\n                className={cn(\n                  \"max-w-[85%] rounded-2xl px-3 py-2\",\n                  m.role === \"user\"\n                    ? \"bg-brand text-white\"\n                    : \"border border-border-subtle bg-background\"\n                )}\n              >\n                {m.content}\n              </p>\n              {m.role === \"user\" ? (\n                <User className=\"mt-0.5 size-4 shrink-0 text-muted-foreground\" />\n              ) : null}\n            </div>\n          ))\n        )}\n        {error ? <p className=\"text-sm text-destructive\">{error}</p> : null}\n      </div>\n      <form\n        onSubmit={onSubmit}\n        className=\"flex flex-col gap-2 border-t border-border-subtle p-3\"\n      >\n        <div className=\"flex gap-2\">\n          <input\n            value={input}\n            onChange={(e) => setInput(e.target.value)}\n            placeholder={CONTENT.placeholder}\n            className=\"min-w-0 flex-1 rounded-lg border border-border-subtle bg-background px-4 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n          />\n          <button\n            type=\"submit\"\n            disabled={loading}\n            className=\"inline-flex size-10 items-center justify-center rounded-lg bg-brand text-white disabled:opacity-60\"\n            aria-label=\"Send\"\n          >\n            {loading ? (\n              <Loader2 className=\"size-4 animate-spin\" />\n            ) : (\n              <Send className=\"size-4\" />\n            )}\n          </button>\n        </div>\n        {(() => {\n          const lastUser = [...messages].reverse().find((m) => m.role === \"user\")\n          if (!lastUser) return null\n          const href = buildOgHref(briefFromScopeMessage(lastUser.content), {\n            path: CONTENT.ogPath,\n          })\n          return (\n            <p className=\"text-center text-[10px] text-muted-foreground\">\n              <Link\n                href={href}\n                className=\"text-brand underline-offset-2 hover:underline\"\n              >\n                Draft OG card\n              </Link>{\" \"}\n              from your last message\n            </p>\n          )\n        })()}\n      </form>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/scope-chat.tsx"
    }
  ],
  "type": "registry:block"
}