{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tool-receipt",
  "type": "registry:component",
  "title": "Tool Receipt",
  "description": "Every tool call your agent makes prints as a little thermal receipt with its time, tokens and cost; tap one to unroll the arguments and result, failed calls get a red VOID stamp and a Retry, and a whole run stacks up on a spike with a running total.",
  "categories": [
    "component",
    "Mission Control"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://coss.com/ui/r/button.json"
  ],
  "files": [
    {
      "path": "registry/devl/tool-receipt.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { CheckIcon, ChevronDownIcon, RotateCwIcon } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { type CSSProperties, type ReactNode, useEffect, useId, useRef, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ReceiptStatus = \"running\" | \"success\" | \"error\";\n\nexport interface ToolReceiptCall {\n  id: string;\n  name: string;\n  summary?: string;\n  status: ReceiptStatus;\n  time?: string;\n  durationMs?: number;\n  tokens?: number;\n  cost?: number;\n  args?: Record<string, unknown>;\n  result?: unknown;\n  error?: string;\n}\n\nexport interface ToolReceiptProps {\n  call: ToolReceiptCall;\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  onRetry?: (id: string) => void;\n  currency?: string;\n  className?: string;\n  punched?: boolean;\n}\n\nexport interface ToolReceiptSpikeProps {\n  calls: ToolReceiptCall[];\n  title?: string;\n  newestFirst?: boolean;\n  defaultOpenId?: string;\n  onRetry?: (id: string) => void;\n  currency?: string;\n  className?: string;\n}\n\nconst spring = { type: \"spring\", stiffness: 420, damping: 38, mass: 0.9 } as const;\nconst outline: CSSProperties = {\n  filter:\n    \"drop-shadow(0 0 0.5px color-mix(in oklab, var(--color-foreground) 30%, transparent)) drop-shadow(0 1px 1.5px color-mix(in oklab, var(--color-foreground) 7%, transparent))\",\n};\nconst HOLE = { x: 16, y: 22, r: 5 };\n\nfunction paperMask(punched: boolean): CSSProperties {\n  const body = \"linear-gradient(black, black) top / 100% calc(100% - 5px) no-repeat\";\n  const teeth = \"conic-gradient(from -45deg at 50% 100%, black 90deg, transparent 0) bottom / 10px 5px repeat-x\";\n  if (!punched) return { mask: `${body}, ${teeth}` };\n  const hole = `radial-gradient(circle at ${HOLE.x}px ${HOLE.y}px, transparent ${HOLE.r}px, black ${HOLE.r + 0.5}px) 0 0 / 100% 100% no-repeat`;\n  return { mask: `${hole}, ${body}, ${teeth}`, maskComposite: \"intersect, add, add\" } as CSSProperties;\n}\n\nfunction fmtMs(ms: number) {\n  return ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(ms < 10_000 ? 2 : 1)}s`;\n}\nconst fmtTokens = (n: number) => n.toLocaleString(\"en-US\");\nconst fmtCost = (n: number, cur: string) => `${cur}${n.toFixed(4)}`;\n\nfunction scalar(v: unknown) {\n  if (typeof v === \"string\") return v;\n  if (v === null || v === undefined) return \"—\";\n  return String(v);\n}\n\nfunction toLines(value: unknown, prefix = \"\"): [string, string][] {\n  if (value === null || typeof value !== \"object\") return [[prefix || \"value\", scalar(value)]];\n  if (Array.isArray(value)) {\n    if (value.every((v) => v === null || typeof v !== \"object\")) return [[prefix || \"items\", value.map(scalar).join(\", \")]];\n    return value.flatMap((v, i) => toLines(v, `${prefix}[${i}]`));\n  }\n  return Object.entries(value).flatMap(([k, v]) => toLines(v, prefix ? `${prefix}.${k}` : k));\n}\n\nfunction useElapsed(status: ReceiptStatus, durationMs?: number) {\n  const [elapsed, setElapsed] = useState(0);\n  const start = useRef(0);\n  useEffect(() => {\n    if (status !== \"running\") return;\n    start.current = performance.now();\n    setElapsed(0);\n    const t = setInterval(() => setElapsed(performance.now() - start.current), 100);\n    return () => clearInterval(t);\n  }, [status]);\n  return status === \"running\" ? elapsed : (durationMs ?? elapsed);\n}\n\nfunction Barcode({ seed }: { seed: string }) {\n  let h = 2166136261;\n  const bars: number[] = [];\n  for (let i = 0; i < 44; i++) {\n    h ^= seed.charCodeAt(i % seed.length) + i;\n    h = Math.imul(h, 16777619) >>> 0;\n    bars.push((h % 3) + 1);\n  }\n  return (\n    <span aria-hidden className=\"flex h-6 items-stretch gap-[1.5px] opacity-80\">\n      {bars.map((w, i) => (\n        <span className=\"bg-foreground\" key={i} style={{ width: w }} />\n      ))}\n    </span>\n  );\n}\n\nfunction Leader({ label, value, strong, className }: { label: ReactNode; value: ReactNode; strong?: boolean; className?: string }) {\n  return (\n    <div className={cn(\"flex items-baseline gap-2\", strong ? \"text-foreground\" : \"text-muted-foreground\", className)}>\n      <span className={cn(\"min-w-0 shrink truncate\", strong && \"font-medium\")}>{label}</span>\n      <span aria-hidden className=\"min-w-4 flex-1 translate-y-[-3px] border-current/35 border-b border-dotted\" />\n      <span className={cn(\"max-w-[62%] shrink-0 truncate text-right tabular-nums\", strong ? \"font-medium\" : \"text-foreground\")}>\n        {value}\n      </span>\n    </div>\n  );\n}\n\nfunction Section({ title, lines }: { title: string; lines: [string, string][] }) {\n  return (\n    <div className=\"flex flex-col gap-1\">\n      <span className=\"text-[11px] text-muted-foreground uppercase tracking-[0.14em]\">{title}</span>\n      {lines.map(([k, v]) => (\n        <Leader key={k} label={k} value={v} />\n      ))}\n    </div>\n  );\n}\n\nconst Rule = ({ double }: { double?: boolean }) => (\n  <div aria-hidden className={cn(\"border-foreground/20\", double ? \"h-[3px] border-y\" : \"border-t border-dashed\")} />\n);\n\nexport function ToolReceipt({\n  call,\n  defaultOpen = false,\n  open: openProp,\n  onOpenChange,\n  onRetry,\n  currency = \"$\",\n  className,\n  punched = false,\n}: ToolReceiptProps) {\n  const reduce = useReducedMotion();\n  const bodyId = useId();\n  const [openState, setOpenState] = useState(defaultOpen);\n  const open = openProp ?? openState;\n  const setOpen = (next: boolean) => {\n    setOpenState(next);\n    onOpenChange?.(next);\n  };\n  const elapsed = useElapsed(call.status, call.durationMs);\n  const running = call.status === \"running\";\n  const failed = call.status === \"error\";\n  const argLines = call.args ? toLines(call.args) : [];\n  const resultLines = call.result !== undefined && !failed ? toLines(call.result) : [];\n\n  return (\n    <div className={cn(\"relative w-full\", className)} style={outline}>\n      <div\n        className={cn(\"relative bg-card pb-5 font-mono text-card-foreground text-xs\", punched ? \"pl-8\" : \"pl-4\")}\n        style={paperMask(punched)}\n      >\n        <button\n          aria-controls={bodyId}\n          aria-expanded={open}\n          className=\"group flex w-full flex-col gap-1 py-3 pr-4 text-left outline-none focus-visible:bg-accent\"\n          onClick={() => setOpen(!open)}\n          type=\"button\"\n        >\n          <span className=\"flex w-full items-center gap-2\">\n            <StatusGlyph status={call.status} />\n            <span className=\"min-w-0 flex-1 truncate font-medium text-foreground text-sm\">{call.name}</span>\n            <span className={cn(\"tabular-nums\", running ? \"text-foreground\" : \"text-muted-foreground\")}>{fmtMs(elapsed)}</span>\n            <ChevronDownIcon\n              aria-hidden\n              className={cn(\n                \"size-3.5 text-muted-foreground transition-transform duration-200 ease-out group-hover:text-foreground\",\n                open && \"rotate-180\",\n              )}\n            />\n          </span>\n          {call.summary && <span className=\"truncate pl-6 text-muted-foreground\">{call.summary}</span>}\n        </button>\n\n        <div className=\"flex flex-col gap-2 pr-4\">\n          <Rule />\n          <AnimatePresence initial={false}>\n            {open && (\n              <motion.div\n                animate={{ height: \"auto\", opacity: 1 }}\n                className=\"overflow-hidden\"\n                exit={{ height: 0, opacity: 0 }}\n                id={bodyId}\n                initial={{ height: 0, opacity: 0 }}\n                key=\"body\"\n                transition={reduce ? { duration: 0 } : { height: spring, opacity: { duration: 0.18, ease: \"easeOut\" } }}\n              >\n                <motion.div\n                  animate={{ y: 0 }}\n                  className=\"flex flex-col gap-2\"\n                  exit={{ y: -6 }}\n                  initial={{ y: reduce ? 0 : -10 }}\n                  transition={spring}\n                >\n                  {argLines.length > 0 && <Section lines={argLines} title=\"Arguments\" />}\n                  {resultLines.length > 0 && (\n                    <>\n                      <Rule />\n                      <Section lines={resultLines} title=\"Result\" />\n                    </>\n                  )}\n                  {failed && call.error && (\n                    <>\n                      <Rule />\n                      <div className=\"flex flex-col gap-1\">\n                        <span className=\"text-[11px] text-muted-foreground uppercase tracking-[0.14em]\">Error</span>\n                        <span className=\"text-pretty text-destructive-foreground\">{call.error}</span>\n                      </div>\n                    </>\n                  )}\n                  <Rule />\n                  <div className=\"flex flex-col gap-1\">\n                    <Leader label=\"Time\" value={running ? \"printing…\" : fmtMs(elapsed)} />\n                    <Leader label=\"Tokens\" value={call.tokens !== undefined ? fmtTokens(call.tokens) : \"—\"} />\n                  </div>\n                  <div className=\"flex items-end justify-between gap-3 pt-1\">\n                    <Barcode seed={call.id} />\n                    <span className=\"text-right text-muted-foreground\">\n                      {call.id}\n                      {call.time && <span className=\"block tabular-nums\">{call.time}</span>}\n                    </span>\n                  </div>\n                  <Rule double />\n                </motion.div>\n              </motion.div>\n            )}\n          </AnimatePresence>\n\n          <div className=\"flex min-h-5 items-center gap-2\">\n            {failed ? (\n              <>\n                <span className=\"min-w-0 flex-1 truncate text-destructive-foreground\">{open ? \"Not charged\" : (call.error ?? \"Failed\")}</span>\n                {onRetry && (\n                  <Button className=\"-my-1 font-sans\" onClick={() => onRetry(call.id)} size=\"xs\" variant=\"outline\">\n                    <RotateCwIcon />\n                    Retry\n                  </Button>\n                )}\n              </>\n            ) : (\n              <Leader\n                className=\"flex-1\"\n                label={running ? \"Printing\" : open ? \"Total\" : `${call.tokens !== undefined ? fmtTokens(call.tokens) : \"—\"} tokens`}\n                strong={!running}\n                value={\n                  running ? (\n                    <PrintHead />\n                  ) : call.cost !== undefined ? (\n                    fmtCost(call.cost, currency)\n                  ) : (\n                    \"—\"\n                  )\n                }\n              />\n            )}\n          </div>\n        </div>\n\n        <AnimatePresence>\n          {failed && (\n            <motion.span\n              animate={{ opacity: 1, scale: 1, rotate: -12 }}\n              aria-hidden\n              className=\"pointer-events-none absolute top-7 right-24 rounded-md border-2 border-destructive/75 px-2 py-0.5 pl-[calc(--spacing(2)+0.24em)] font-bold text-base text-destructive-foreground/90 tracking-[0.24em]\"\n              exit={{ opacity: 0, scale: 1.04, transition: { duration: 0.15 } }}\n              initial={reduce ? { opacity: 0, scale: 1, rotate: -12 } : { opacity: 0, scale: 1.5, rotate: -18 }}\n              transition={reduce ? { duration: 0.15 } : { type: \"spring\", stiffness: 700, damping: 32 }}\n            >\n              VOID\n            </motion.span>\n          )}\n        </AnimatePresence>\n      </div>\n    </div>\n  );\n}\n\nfunction PrintHead() {\n  return (\n    <span className=\"inline-flex gap-[3px]\" role=\"status\">\n      <span className=\"sr-only\">Running</span>\n      {[0, 1, 2].map((i) => (\n        <span\n          className=\"size-1 animate-pulse rounded-full bg-foreground/60 motion-reduce:animate-none\"\n          key={i}\n          style={{ animationDelay: `${i * 160}ms`, animationDuration: \"1s\" }}\n        />\n      ))}\n    </span>\n  );\n}\n\nfunction StatusGlyph({ status }: { status: ReceiptStatus }) {\n  return (\n    <span className=\"relative flex size-4 shrink-0 items-center justify-center\">\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        {status === \"running\" ? (\n          <motion.span\n            animate={{ opacity: 1, scale: 1 }}\n            aria-label=\"Running\"\n            className=\"size-3 animate-spin rounded-full border-[1.5px] border-foreground/20 border-t-foreground motion-reduce:animate-none\"\n            exit={{ opacity: 0, scale: 0.8 }}\n            initial={{ opacity: 0, scale: 0.8 }}\n            key=\"run\"\n          />\n        ) : status === \"success\" ? (\n          <motion.span\n            animate={{ opacity: 1, scale: 1 }}\n            aria-label=\"Done\"\n            className=\"flex size-4 items-center justify-center rounded-full bg-success/15 text-success-foreground\"\n            exit={{ opacity: 0, scale: 0.8 }}\n            initial={{ opacity: 0, scale: 0.6 }}\n            key=\"ok\"\n            transition={{ type: \"spring\", stiffness: 600, damping: 30 }}\n          >\n            <CheckIcon className=\"size-2.5\" strokeWidth={3} />\n          </motion.span>\n        ) : (\n          <motion.span\n            animate={{ opacity: 1, scale: 1 }}\n            aria-label=\"Failed\"\n            className=\"size-2 rounded-full bg-destructive\"\n            exit={{ opacity: 0, scale: 0.8 }}\n            initial={{ opacity: 0, scale: 0.6 }}\n            key=\"err\"\n          />\n        )}\n      </AnimatePresence>\n    </span>\n  );\n}\n\nconst DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];\n\nfunction Roll({ text, className }: { text: string; className?: string }) {\n  const reduce = useReducedMotion();\n  let d = 0;\n  let o = 0;\n  const glyphs = [...text]\n    .reverse()\n    .map((c) => ({ c, digit: c >= \"0\" && c <= \"9\", key: c >= \"0\" && c <= \"9\" ? `d${d++}` : `s${o++}` }))\n    .reverse();\n  return (\n    <span className={cn(\"inline-flex whitespace-nowrap tabular-nums leading-none\", className)}>\n      <span className=\"sr-only\">{text}</span>\n      <span aria-hidden className=\"inline-flex [--row:1.2em]\">\n        {glyphs.map(({ c, digit, key }) =>\n          digit ? (\n            <span className=\"relative inline-block h-(--row) overflow-hidden\" key={key}>\n              <span className=\"invisible block leading-(--row)\">0</span>\n              <motion.span\n                animate={{ y: `${-Number(c) * 10}%` }}\n                className=\"absolute inset-x-0 top-0 flex flex-col items-center\"\n                initial={false}\n                transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 280, damping: 32 }}\n              >\n                {DIGITS.map((n) => (\n                  <span className=\"block h-(--row) leading-(--row)\" key={n}>\n                    {n}\n                  </span>\n                ))}\n              </motion.span>\n            </span>\n          ) : (\n            <span className=\"inline-block h-(--row) leading-(--row)\" key={key}>\n              {c}\n            </span>\n          ),\n        )}\n      </span>\n    </span>\n  );\n}\n\nexport function ToolReceiptSpike({\n  calls,\n  title = \"Tool calls\",\n  newestFirst = true,\n  defaultOpenId,\n  onRetry,\n  currency = \"$\",\n  className,\n}: ToolReceiptSpikeProps) {\n  const reduce = useReducedMotion();\n  const ordered = newestFirst ? [...calls].reverse() : calls;\n  const settled = calls.filter((c) => c.status === \"success\");\n  const cost = settled.reduce((s, c) => s + (c.cost ?? 0), 0);\n  const tokens = settled.reduce((s, c) => s + (c.tokens ?? 0), 0);\n  const ms = calls.reduce((s, c) => s + (c.status === \"running\" ? 0 : (c.durationMs ?? 0)), 0);\n  const running = calls.filter((c) => c.status === \"running\").length;\n  const voided = calls.filter((c) => c.status === \"error\").length;\n\n  return (\n    <div className={cn(\"flex w-[400px] max-w-full flex-col gap-4\", className)}>\n      <div className=\"flex items-end justify-between gap-4 px-1\">\n        <div className=\"flex min-w-0 flex-col gap-1\">\n          <span className=\"truncate font-medium text-sm\">{title}</span>\n          <span aria-live=\"polite\" className=\"text-muted-foreground text-xs tabular-nums\">\n            {calls.length} {calls.length === 1 ? \"call\" : \"calls\"} · {fmtMs(ms)}\n            {running > 0 ? ` · ${running} printing` : \"\"}\n            {voided > 0 ? ` · ${voided} void` : \"\"}\n          </span>\n        </div>\n        <div className=\"flex shrink-0 flex-col items-end gap-1\">\n          <Roll className=\"font-medium font-mono text-sm\" text={fmtCost(cost, currency)} />\n          <span className=\"font-mono text-muted-foreground text-xs\">\n            <Roll text={fmtTokens(tokens)} /> tok\n          </span>\n        </div>\n      </div>\n\n      <div className=\"relative\">\n        <span\n          aria-hidden\n          className=\"absolute top-[-16px] bottom-1 left-[14.5px] w-[3px] bg-linear-to-b from-foreground/20 via-foreground/40 to-foreground/50\"\n          style={{ clipPath: \"polygon(50% 0, 100% 12px, 100% 100%, 0 100%, 0 12px)\" }}\n        />\n        <span aria-hidden className=\"absolute bottom-0 left-[4px] h-1.5 w-6 rounded-full bg-foreground/20\" />\n        <ol className=\"relative flex flex-col gap-2.5 pb-4\">\n          <AnimatePresence initial={false} mode=\"popLayout\">\n            {ordered.map((call) => (\n              <motion.li\n                animate={{ opacity: 1, y: 0 }}\n                exit={{ opacity: 0 }}\n                initial={reduce ? { opacity: 0 } : { opacity: 0, y: -18 }}\n                key={call.id}\n                layout={reduce ? false : \"position\"}\n                transition={spring}\n              >\n                <ToolReceipt call={call} currency={currency} defaultOpen={call.id === defaultOpenId} onRetry={onRetry} punched />\n              </motion.li>\n            ))}\n          </AnimatePresence>\n          {calls.length === 0 && (\n            <li className=\"ml-8 rounded-lg border border-dashed px-4 py-6 text-center text-muted-foreground text-xs\">\n              Waiting for the first tool call\n            </li>\n          )}\n        </ol>\n      </div>\n    </div>\n  );\n}\n"
    }
  ]
}