{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "conveyor",
  "type": "registry:component",
  "title": "Conveyor",
  "description": "A job queue on a little conveyor belt: jobs drop on, ride into whichever worker frees up and land in Done or Failed, and when a rate limit hits the belt stops while the backlog chart shows the pile-up and the drain.",
  "categories": [
    "component",
    "Mission Control"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/conveyor.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { CheckIcon, PauseIcon, XIcon } from \"lucide-react\";\nimport { AnimatePresence, LayoutGroup, MotionConfig, motion, useReducedMotion } from \"motion/react\";\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nfunction useReducedMotionAfterMount() {\n  const prefers = useReducedMotion();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  return mounted && Boolean(prefers);\n}\n\nexport interface ConveyorProps {\n  title?: string;\n  jobNames?: string[];\n  workers?: number;\n  jobsPerSecond?: number;\n  rateLimit?: { every: number; for: number } | false;\n  failRate?: number;\n  warmupSeconds?: number;\n  className?: string;\n}\n\ninterface Job {\n  id: number;\n  name: string;\n  workSeconds: number;\n  progress: number;\n  error?: string;\n}\n\ninterface Sample {\n  depth: number;\n  limited: boolean;\n}\n\nconst TICK = 100;\nconst SAMPLE_EVERY = 5;\nconst SAMPLES = 60;\nconst errors = [\"timeout after 30s\", \"upstream 502\", \"invalid input\"];\n\nfunction seededRandom(seed: number) {\n  let a = seed;\n  return () => {\n    a |= 0;\n    a = (a + 0x6d2b79f5) | 0;\n    let t = Math.imul(a ^ (a >>> 15), 1 | a);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nconst defaultNames = [\"resize image\", \"transcode clip\", \"embed document\", \"summarize thread\", \"extract invoice\"];\nconst defaultLimit = { every: 22, for: 6 };\n\nconst spring = { type: \"spring\", stiffness: 420, damping: 34 } as const;\n\nfunction Chip({ job, tone = \"queued\" }: { job: Job; tone?: \"queued\" | \"running\" | \"done\" | \"failed\" }) {\n  return (\n    <motion.span\n      className={cn(\n        \"inline-flex h-6 shrink-0 items-center justify-center rounded-md border px-1.5 font-mono text-[11px] tabular-nums\",\n        tone === \"queued\" && \"border-border bg-background text-muted-foreground\",\n        tone === \"running\" && \"border-info/30 bg-info/10 text-info-foreground\",\n        tone === \"done\" && \"border-success/25 bg-success/8 text-success-foreground\",\n        tone === \"failed\" && \"border-destructive/25 bg-destructive/8 text-destructive-foreground\",\n      )}\n      layoutId={`job-${job.id}`}\n      title={job.error ? `${job.name}: ${job.error}` : job.name}\n      transition={spring}\n    >\n      #{job.id}\n    </motion.span>\n  );\n}\n\nfunction Roll({ value }: { value: number }) {\n  return (\n    <span className=\"relative inline-flex h-[1lh] overflow-hidden tabular-nums\">\n      <AnimatePresence initial={false} mode=\"popLayout\">\n        <motion.span\n          animate={{ y: 0, opacity: 1 }}\n          exit={{ y: \"-100%\", opacity: 0 }}\n          initial={{ y: \"100%\", opacity: 0 }}\n          key={value}\n          transition={spring}\n        >\n          {value}\n        </motion.span>\n      </AnimatePresence>\n    </span>\n  );\n}\n\nfunction DepthChart({ samples, max }: { samples: Sample[]; max: number }) {\n  const w = 300;\n  const h = 48;\n  const step = w / (SAMPLES - 1);\n  const offset = SAMPLES - samples.length;\n  const pts = samples.map((s, i) => [(i + offset) * step, h - 2 - (s.depth / max) * (h - 6)] as const);\n  const line = pts.map(([x, y], i) => `${i ? \"L\" : \"M\"}${x.toFixed(1)} ${y.toFixed(1)}`).join(\" \");\n  const area = pts.length ? `${line} L${pts[pts.length - 1][0].toFixed(1)} ${h} L${pts[0][0].toFixed(1)} ${h} Z` : \"\";\n  const bands: { x: number; w: number }[] = [];\n  samples.forEach((s, i) => {\n    if (!s.limited) return;\n    const x = (i + offset) * step - step / 2;\n    const last = bands[bands.length - 1];\n    if (last && Math.abs(last.x + last.w - x) < 0.5) last.w += step;\n    else bands.push({ x, w: step });\n  });\n  const tip = pts[pts.length - 1];\n  return (\n    <svg aria-hidden className=\"h-12 w-full overflow-visible\" preserveAspectRatio=\"none\" viewBox={`0 0 ${w} ${h}`}>\n      {bands.map((b) => (\n        <rect className=\"fill-warning/12\" height={h} key={b.x} width={b.w} x={Math.max(b.x, 0)} y={0} />\n      ))}\n      <line className=\"stroke-border\" x1={0} x2={w} y1={h - 0.5} y2={h - 0.5} />\n      {area && <path className=\"fill-foreground/8\" d={area} />}\n      {line && (\n        <path\n          className=\"stroke-foreground/60\"\n          d={line}\n          fill=\"none\"\n          strokeLinejoin=\"round\"\n          strokeWidth={1.5}\n          vectorEffect=\"non-scaling-stroke\"\n        />\n      )}\n      {tip && (\n        <circle className=\"fill-foreground\" cx={tip[0]} cy={tip[1]} r={2.5} vectorEffect=\"non-scaling-stroke\" />\n      )}\n    </svg>\n  );\n}\n\nexport function Conveyor({\n  title = \"Job queue\",\n  jobNames = defaultNames,\n  workers: workerCount = 3,\n  jobsPerSecond = 1.35,\n  rateLimit = defaultLimit,\n  failRate = 0.07,\n  warmupSeconds = 26,\n  className,\n}: ConveyorProps) {\n  const reduce = useReducedMotionAfterMount();\n  const [queue, setQueue] = useState<Job[]>([]);\n  const [slots, setSlots] = useState<(Job | null)[]>(() => Array.from({ length: workerCount }, () => null));\n  const [done, setDone] = useState<Job[]>([]);\n  const [failed, setFailed] = useState<Job[]>([]);\n  const [counts, setCounts] = useState({ done: 0, failed: 0 });\n  const [samples, setSamples] = useState<Sample[]>([]);\n  const [limitLeft, setLimitLeft] = useState(0);\n\n  const sim = useRef({\n    t: 0,\n    nextId: 481,\n    rand: seededRandom(Date.now() % 100000),\n    queue: [] as Job[],\n    slots: [] as (Job | null)[],\n    limitUntil: 0,\n    nextLimit: 0,\n    ticks: 0,\n  });\n\n  const stripRef = useRef<HTMLDivElement>(null);\n  const [fit, setFit] = useState(7);\n  useLayoutEffect(() => {\n    const el = stripRef.current;\n    if (!el) return;\n    const ro = new ResizeObserver(() => setFit(Math.max(3, Math.floor((el.clientWidth + 4) / 44) - 1)));\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, []);\n\n  useEffect(() => {\n    const s = sim.current;\n    Object.assign(s, { t: 0, ticks: 0, limitUntil: 0, nextId: 481, queue: [] });\n    s.slots = Array.from({ length: workerCount }, () => null);\n    setSamples([]);\n    setDone([]);\n    setFailed([]);\n    setCounts({ done: 0, failed: 0 });\n    s.nextLimit = rateLimit ? rateLimit.every * 0.45 : Number.POSITIVE_INFINITY;\n    for (let i = 0; i < 3; i++) {\n      s.queue.push({ id: s.nextId++, name: jobNames[i % jobNames.length], workSeconds: 1 + s.rand() * 0.8, progress: 0 });\n    }\n    setQueue([...s.queue]);\n\n    const tick = () => {\n      const dt = TICK / 1000;\n      s.t += dt;\n      s.ticks += 1;\n      const limited = s.t < s.limitUntil;\n\n      if (rateLimit && !limited && s.t >= s.nextLimit) {\n        s.limitUntil = s.t + rateLimit.for;\n        s.nextLimit = s.t + rateLimit.every;\n      }\n\n      if (s.rand() < jobsPerSecond * dt) {\n        s.queue.push({\n          id: s.nextId++,\n          name: jobNames[s.nextId % jobNames.length],\n          workSeconds: 1 + s.rand() * 0.8,\n          progress: 0,\n        });\n      }\n\n      const landedDone: Job[] = [];\n      const landedFailed: Job[] = [];\n      s.slots = s.slots.map((job) => {\n        if (!job) return null;\n        if (s.t < s.limitUntil) return job;\n        const progress = Math.min(job.progress + dt / job.workSeconds, 1);\n        if (progress >= 1) {\n          if (s.rand() < failRate) landedFailed.push({ ...job, progress: 1, error: errors[job.id % errors.length] });\n          else landedDone.push({ ...job, progress: 1 });\n          return null;\n        }\n        return { ...job, progress };\n      });\n\n      if (s.t >= s.limitUntil) {\n        const free = s.slots.findIndex((x) => x === null);\n        const next = s.queue[0];\n        if (free !== -1 && next) {\n          s.queue = s.queue.slice(1);\n          s.slots[free] = next;\n        }\n      }\n\n      setQueue([...s.queue]);\n      setSlots([...s.slots]);\n      if (landedDone.length) {\n        setDone((d) => [...landedDone, ...d].slice(0, 3));\n        setCounts((c) => ({ ...c, done: c.done + landedDone.length }));\n      }\n      if (landedFailed.length) {\n        setFailed((f) => [...landedFailed, ...f].slice(0, 1));\n        setCounts((c) => ({ ...c, failed: c.failed + landedFailed.length }));\n      }\n      setLimitLeft(Math.max(0, Math.ceil(s.limitUntil - s.t)));\n      if (s.ticks % SAMPLE_EVERY === 0) {\n        const sample = { depth: s.queue.length, limited: s.t < s.limitUntil };\n        setSamples((list) => [...list, sample].slice(-SAMPLES));\n      }\n    };\n    for (let i = 0; i < warmupSeconds * (1000 / TICK); i++) tick();\n    const iv = setInterval(tick, TICK);\n    return () => {\n      clearInterval(iv);\n      s.queue = [];\n    };\n    // Restart only when the shape of the simulation changes.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [workerCount, jobsPerSecond, failRate, rateLimit ? rateLimit.every : 0, rateLimit ? rateLimit.for : 0, jobNames.join(\"|\"), warmupSeconds]);\n\n  const limited = limitLeft > 0;\n  const max = useMemo(() => Math.max(8, ...samples.map((s) => s.depth)) * 1.15, [samples]);\n  const shown = queue.slice(0, fit);\n  const hidden = queue.length - shown.length;\n  const busy = slots.filter(Boolean).length;\n\n  return (\n    <MotionConfig reducedMotion=\"user\">\n    <div className={cn(\"w-full max-w-[460px] rounded-2xl border bg-card shadow-xs/5\", className)}>\n      <div className=\"flex items-start justify-between gap-3 px-4 pt-3.5\">\n        <div className=\"min-w-0\">\n          <h3 className=\"truncate font-medium text-sm\">{title}</h3>\n          <p className=\"text-muted-foreground text-xs tabular-nums\">\n            {busy}/{workerCount} workers busy\n          </p>\n        </div>\n        <AnimatePresence initial={false} mode=\"wait\">\n          {limited ? (\n            <motion.span\n              animate={{ opacity: 1, scale: 1 }}\n              className=\"flex shrink-0 items-center gap-1.5 rounded-full bg-warning/10 px-2 py-0.5 font-medium text-warning-foreground text-xs tabular-nums\"\n              exit={{ opacity: 0, scale: 0.95, transition: { duration: 0.1 } }}\n              initial={{ opacity: 0, scale: 0.95 }}\n              key=\"limited\"\n              transition={spring}\n            >\n              <PauseIcon className=\"size-3\" />\n              Rate limited · {limitLeft}s\n            </motion.span>\n          ) : (\n            <motion.span\n              animate={{ opacity: 1, scale: 1 }}\n              className=\"flex shrink-0 items-center gap-1.5 rounded-full bg-muted px-2 py-0.5 text-muted-foreground text-xs\"\n              exit={{ opacity: 0, scale: 0.95, transition: { duration: 0.1 } }}\n              initial={{ opacity: 0, scale: 0.95 }}\n              key=\"running\"\n              transition={spring}\n            >\n              <span className=\"relative flex size-1.5\">\n                {!reduce && <span className=\"absolute inset-0 animate-ping rounded-full bg-success/70\" />}\n                <span className=\"relative size-1.5 rounded-full bg-success\" />\n              </span>\n              Processing\n            </motion.span>\n          )}\n        </AnimatePresence>\n      </div>\n\n      <div className=\"mx-4 mt-3 rounded-xl border px-3 pt-2.5 pb-2\">\n        <div className=\"flex items-center justify-between gap-3 text-xs\">\n          <span className=\"text-muted-foreground\">Backlog</span>\n          <AnimatePresence>\n            {samples.some((x) => x.limited) && (\n              <motion.span\n                animate={{ opacity: 1 }}\n                className=\"flex items-center gap-1.5 text-muted-foreground\"\n                exit={{ opacity: 0 }}\n                initial={{ opacity: 0 }}\n              >\n                <span className=\"size-2 rounded-[calc(var(--radius)*0.2)] bg-warning/30\" />\n                Rate limited\n              </motion.span>\n            )}\n          </AnimatePresence>\n        </div>\n        <div className=\"flex items-end gap-4\">\n          <p className=\"w-8 shrink-0 font-medium text-2xl leading-8 tabular-nums\">\n            <Roll value={queue.length} />\n          </p>\n          <div className=\"min-w-0 flex-1\">\n            <DepthChart max={max} samples={samples} />\n          </div>\n        </div>\n      </div>\n\n      <LayoutGroup>\n        <div className=\"px-4 pt-4\">\n          <p className=\"mb-1.5 text-muted-foreground text-xs\">Queued</p>\n          <div className=\"relative flex h-6 items-center gap-1\" ref={stripRef}>\n            <AnimatePresence initial={false} mode=\"popLayout\">\n              {shown.map((job) => (\n                <motion.span\n                  animate={{ opacity: 1, y: 0 }}\n                  className=\"flex\"\n                  initial={{ opacity: 0, y: -14 }}\n                  key={job.id}\n                  layout\n                  transition={spring}\n                >\n                  <Chip job={job} />\n                </motion.span>\n              ))}\n              {hidden > 0 && (\n                <motion.span\n                  animate={{ opacity: 1 }}\n                  className=\"ml-0.5 shrink-0 text-muted-foreground text-xs tabular-nums\"\n                  initial={{ opacity: 0 }}\n                  key=\"more\"\n                  layout\n                >\n                  +{hidden}\n                </motion.span>\n              )}\n              {queue.length === 0 && (\n                <motion.span\n                  animate={{ opacity: 1 }}\n                  className=\"text-muted-foreground text-xs\"\n                  initial={{ opacity: 0 }}\n                  key=\"empty\"\n                >\n                  Empty — workers are keeping up\n                </motion.span>\n              )}\n            </AnimatePresence>\n          </div>\n          <motion.div\n            animate={limited || reduce ? { backgroundPositionX: \"0px\" } : { backgroundPositionX: [\"0px\", \"-10px\"] }}\n            aria-hidden\n            className=\"mt-1.5 h-1 rounded-full bg-[repeating-linear-gradient(90deg,var(--color-border)_0_6px,transparent_6px_10px)] opacity-80\"\n            transition={limited || reduce ? { duration: 0.2 } : { duration: 0.6, ease: \"linear\", repeat: Number.POSITIVE_INFINITY }}\n          />\n        </div>\n\n        <ul className=\"space-y-1.5 px-4 pt-3\" aria-label=\"Workers\">\n          {slots.map((job, i) => (\n            <li className=\"flex items-center gap-2\" key={i}>\n              <span className=\"w-5 shrink-0 font-mono text-[10px] text-muted-foreground\">w{i + 1}</span>\n              <div\n                className={cn(\n                  \"relative flex h-8 min-w-0 flex-1 items-center gap-2 rounded-lg border pr-2.5 pl-1\",\n                  job ? \"bg-background\" : \"border-dashed bg-transparent\",\n                )}\n              >\n                {job && (\n                  <span className=\"absolute inset-0 overflow-hidden rounded-[calc(var(--radius-lg)-1px)]\">\n                  <motion.span\n                    animate={{ scaleX: job.progress }}\n                    className={cn(\n                      \"absolute inset-0 origin-left transition-colors duration-300\",\n                      limited ? \"bg-warning/12\" : \"bg-info/10\",\n                    )}\n                    initial={false}\n                    key={job.id}\n                    transition={{ type: \"tween\", duration: TICK / 1000, ease: \"linear\" }}\n                  />\n                  </span>\n                )}\n                {job ? (\n                  <>\n                    <span className=\"relative flex\">\n                      <Chip job={job} tone=\"running\" />\n                    </span>\n                    <span className=\"relative min-w-0 flex-1 truncate text-xs\">{job.name}</span>\n                    <span\n                      className={cn(\n                        \"relative flex shrink-0 items-center gap-1 font-mono text-[10px] tabular-nums\",\n                        limited ? \"text-warning-foreground\" : \"text-muted-foreground\",\n                      )}\n                    >\n                      {limited && <PauseIcon className=\"size-3\" />}\n                      {Math.round(job.progress * 100)}%\n                    </span>\n                  </>\n                ) : (\n                  <span className=\"pl-1.5 text-muted-foreground text-xs\">{limited ? \"Paused\" : \"Idle\"}</span>\n                )}\n              </div>\n            </li>\n          ))}\n        </ul>\n\n        <div className=\"grid grid-cols-2 gap-2 p-4\">\n          {(\n            [\n              { key: \"done\", label: \"Done\", Icon: CheckIcon, list: done, count: counts.done },\n              { key: \"failed\", label: \"Failed\", Icon: XIcon, list: failed, count: counts.failed },\n            ] as const\n          ).map((col) => (\n            <div className=\"min-w-0 rounded-xl bg-muted/60 p-2.5\" key={col.key}>\n              <div className=\"flex items-center justify-between text-xs\">\n                <span className=\"flex items-center gap-1 text-muted-foreground\">\n                  <col.Icon\n                    className={cn(\"size-3\", col.key === \"done\" ? \"text-success-foreground\" : \"text-destructive-foreground\")}\n                  />\n                  {col.label}\n                </span>\n                <span className=\"font-medium text-sm\">\n                  <Roll value={col.count} />\n                </span>\n              </div>\n              <div className=\"mt-2 flex h-6 gap-1.5\">\n                {col.list.length === 0 && <span className=\"text-muted-foreground text-xs leading-6\">None yet</span>}\n                {col.key === \"failed\" && col.list[0] ? (\n                  <>\n                    <Chip job={col.list[0]} key={col.list[0].id} tone=\"failed\" />\n                    <span className=\"min-w-0 flex-1 truncate text-[11px] text-muted-foreground leading-6\">\n                      {col.list[0].error}\n                    </span>\n                  </>\n                ) : (\n                  col.list.map((job) => <Chip job={job} key={job.id} tone={col.key} />)\n                )}\n              </div>\n            </div>\n          ))}\n        </div>\n      </LayoutGroup>\n    </div>\n    </MotionConfig>\n  );\n}\n"
    }
  ]
}