{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "terminal-block",
  "type": "registry:ui",
  "title": "Terminal Block",
  "description": "Command output in a debossed well with a live agent-status chip and copy key.",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://seamui.dev/r/utils.json",
    "https://seamui.dev/r/motion.json",
    "https://seamui.dev/r/button.json",
    "https://seamui.dev/r/agent-status.json",
    "https://seamui.dev/r/use-copy.json"
  ],
  "files": [
    {
      "path": "registry/seam/ui/terminal-block.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport { Check, Copy, TerminalSquare } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { fades } from \"@/lib/motion\"\nimport { useCopy } from \"@/lib/use-copy\"\nimport { AgentStatus, type AgentState } from \"./agent-status\"\nimport { Button } from \"./button\"\n\n// Lightweight status-line tinting for plain-string output: a cheap pass that\n// greens pass/✓ lines and reds fail/error/✗ lines — no ANSI plumbing, no deps.\n// Errors win over successes so a mixed \"1 failed\" line still reads red; leads\n// on the line's symbol/keyword to avoid false positives (\"no errors found\"\n// stays neutral). Consumers who need real ANSI colors pass their own nodes.\nfunction outputLineClass(line: string): string {\n  const t = line.trimStart()\n  if (\n    /^(?:[✗✘×✖⨯]|error|fail|fatal|panic)/i.test(t) ||\n    /\\b\\d+\\s+(?:failed|errors?)\\b/i.test(t)\n  )\n    return \"text-destructive\"\n  if (\n    /^(?:[✓✔√]|pass)/i.test(t) ||\n    /\\b\\d+\\s+passed\\b/i.test(t) ||\n    /\\ball\\s+(?:tests?\\s+)?passed\\b/i.test(t)\n  )\n    return \"text-emerald-600 dark:text-emerald-500\"\n  if (/^(?:[⚠]|warn)/i.test(t)) return \"text-amber-600 dark:text-amber-500\"\n  if (/^\\$\\s/.test(t)) return \"text-muted-foreground\" // shell prompt echo\n  return \"\"\n}\n\nfunction TintedOutput({ text }: { text: string }) {\n  const lines = text.split(\"\\n\")\n  return (\n    <>\n      {lines.map((line, i) => {\n        const cls = outputLineClass(line)\n        return (\n          <React.Fragment key={i}>\n            {cls ? <span className={cls}>{line}</span> : line}\n            {i < lines.length - 1 ? \"\\n\" : null}\n          </React.Fragment>\n        )\n      })}\n    </>\n  )\n}\n\n// The code-block sibling for command output: a debossed well you read into,\n// with the command and live agent-status in an embossed header. A thread /\n// review element, not a live PTY — output arrives as children.\nfunction TerminalBlock({\n  command,\n  status,\n  copyText,\n  tint = true,\n  className,\n  children,\n  ...props\n}: Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  command: string\n  /** Shared agent vocabulary: working / waiting (needs input) / done / error. */\n  status?: AgentState\n  /** Text the copy key copies; defaults to string children. */\n  copyText?: string\n  /** Auto-tint pass/fail lines when the output is a plain string. Off → verbatim. */\n  tint?: boolean\n  children?: React.ReactNode\n}) {\n  const { copied, copy } = useCopy()\n  const raw = typeof children === \"string\" ? children : undefined\n  const text = copyText ?? raw\n\n  return (\n    <div\n      data-slot=\"terminal-block\"\n      data-status={status}\n      className={cn(\n        \"bg-muted overflow-hidden rounded-lg squircle border border-border/60 shadow-well\",\n        className\n      )}\n      {...props}\n    >\n      <div\n        data-slot=\"terminal-block-header\"\n        // Raised control strip: the header carries the command + status chip +\n        // copy key, so it's a lifted bar (bg-secondary), not part of the debossed\n        // well below — otherwise a settled \"done\" chip reads gray-on-gray.\n        className=\"bg-secondary flex items-center gap-2 border-b border-border/60 px-2 py-1\"\n      >\n        <TerminalSquare\n          aria-hidden\n          className=\"text-muted-foreground size-3.5 shrink-0\"\n        />\n        <code className=\"min-w-0 flex-1 truncate font-mono text-xs\">\n          {command}\n        </code>\n        {status ? <AgentStatus status={status} className=\"shrink-0\" /> : null}\n        {text !== undefined ? (\n          <Button\n            data-slot=\"terminal-block-copy\"\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={() => text !== undefined && copy(text)}\n            aria-label={copied ? \"Copied\" : \"Copy output\"}\n            className=\"text-muted-foreground size-7 shrink-0\"\n          >\n            {/* Confirmation is an opacity crossfade — identical under reduced motion. */}\n            <AnimatePresence mode=\"wait\" initial={false}>\n              <motion.span\n                key={copied ? \"check\" : \"copy\"}\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{ opacity: 0 }}\n                transition={fades.fast}\n                className=\"flex items-center justify-center\"\n              >\n                {copied ? (\n                  <Check className=\"size-3.5\" />\n                ) : (\n                  <Copy className=\"size-3.5\" />\n                )}\n              </motion.span>\n            </AnimatePresence>\n          </Button>\n        ) : null}\n      </div>\n      <div\n        data-slot=\"terminal-block-output\"\n        // Keyboard users can scroll long output.\n        tabIndex={0}\n        className=\"max-h-72 overflow-auto p-3 outline-none\"\n      >\n        <pre className=\"font-mono text-xs leading-relaxed whitespace-pre-wrap\">\n          {tint && raw !== undefined ? <TintedOutput text={raw} /> : children}\n          {status === \"working\" ? (\n            // Ambient cursor pulse — CSS, the Spinner precedent (see #72);\n            // opacity-only so it reads identically under reduced motion.\n            <span aria-hidden className=\"animate-pulse\">\n              ▍\n            </span>\n          ) : null}\n        </pre>\n      </div>\n    </div>\n  )\n}\n\nexport { TerminalBlock }\n",
      "type": "registry:ui",
      "target": "components/ui/terminal-block.tsx"
    }
  ]
}