{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "run-header",
  "type": "registry:ui",
  "title": "Run Header",
  "description": "Header for one agent run: title, agent, model, branch, status, live elapsed clock, tokens, cost vs budget, step progress and Stop/Pause controls.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://kuro.abstergo.fyi/r/kura-theme.json",
    "https://kuro.abstergo.fyi/r/button.json",
    "https://kuro.abstergo.fyi/r/icon-button.json",
    "https://kuro.abstergo.fyi/r/kura-format.json",
    "https://kuro.abstergo.fyi/r/shimmer-text.json",
    "https://kuro.abstergo.fyi/r/status-glyph.json",
    "https://kuro.abstergo.fyi/r/tooltip.json",
    "https://kuro.abstergo.fyi/r/use-elapsed.json",
    "https://kuro.abstergo.fyi/r/use-hotkey.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/kura/run-header/index.ts",
      "type": "registry:ui",
      "target": "components/kura/run-header/index.ts",
      "content": "export {\n  RunHeader,\n  type RunHeaderProgress,\n  type RunHeaderProps,\n  type RunHeaderTokens,\n} from \"./run-header\";\n"
    },
    {
      "path": "registry/kura/run-header/run-header.tsx",
      "type": "registry:ui",
      "target": "components/kura/run-header/run-header.tsx",
      "content": "\"use client\";\n\nimport { GitBranch, Pause, Play, Square } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useElapsed } from \"@/hooks/kura/use-elapsed\";\nimport { useHotkey } from \"@/hooks/kura/use-hotkey\";\nimport { cn } from \"@/lib/utils\";\nimport { formatClock, formatCost, formatTokens } from \"@/lib/kura/format\";\nimport { Button } from \"@/components/kura/button\";\nimport { IconButton } from \"@/components/kura/icon-button\";\nimport { ShimmerText } from \"@/components/kura/shimmer-text\";\nimport { type AgentStatus, StatusGlyph } from \"@/components/kura/status-glyph\";\nimport { Tooltip } from \"@/components/kura/tooltip\";\n\nexport interface RunHeaderTokens {\n  /** Prompt / input tokens, including cache reads. */\n  input: number;\n  /** Completion / output tokens. */\n  output: number;\n}\n\nexport interface RunHeaderProgress {\n  /** Steps finished. */\n  done: number;\n  /** Steps in the plan. */\n  total: number;\n}\n\nexport interface RunHeaderProps extends Omit<React.ComponentProps<\"section\">, \"title\"> {\n  /** What the run is doing, e.g. \"Migrate session API to @acme/auth\". */\n  title: React.ReactNode;\n  /** The agent's name, e.g. \"Kura\". */\n  agent?: string;\n  /** Model id, shown in mono, e.g. \"claude-opus-5-5\". */\n  model?: string;\n  /** Run state. Non-terminal states (queued, running, waiting, paused) keep the clock ticking. */\n  status: AgentStatus;\n  /** When the run started. Drives the live elapsed clock. */\n  startedAt?: Date | number;\n  /** When the run ended. Freezes the clock at endedAt − startedAt. */\n  endedAt?: Date | number;\n  /** Token usage so far. */\n  tokens?: RunHeaderTokens;\n  /** Spend so far in USD. */\n  cost?: number;\n  /** Budget in USD. Shows spend against it with a meter that turns amber at 80% and red past 100%. */\n  budget?: number;\n  /** Git branch the agent works on, shown in mono with a branch icon. */\n  branch?: string;\n  /** Plan progress, shown as \"4 of 7\" with a segmented bar. */\n  progress?: RunHeaderProgress;\n  /** Extra controls placed before Pause / Stop, e.g. an \"Open PR\" button. */\n  actions?: React.ReactNode;\n  /** Shows a Stop control while the run is live. */\n  onStop?: () => void;\n  /** Shows a Pause control while running. */\n  onPause?: () => void;\n  /** Shows a Resume control while paused. */\n  onResume?: () => void;\n  /** `bar` = one dense row; `panel` = title row plus a stats strip. Default \"panel\". */\n  variant?: \"bar\" | \"panel\";\n  /** Bind ⌘/Ctrl+. to Stop and ⌘/Ctrl+Shift+P to Pause/Resume. Only one run header on screen should bind them. Default true. */\n  shortcuts?: boolean;\n}\n\nconst STOP_KEYS = [\"mod\", \".\"] as const;\nconst PAUSE_KEYS = [\"mod\", \"shift\", \"p\"] as const;\n\nconst LIVE: AgentStatus[] = [\"queued\", \"running\", \"waiting\", \"paused\"];\n\nconst STATUS_TEXT: Record<AgentStatus, string> = {\n  queued: \"Queued\",\n  running: \"Running\",\n  waiting: \"Waiting for you\",\n  paused: \"Paused\",\n  done: \"Completed\",\n  failed: \"Failed\",\n  skipped: \"Stopped\",\n};\n\nfunction StatusLabel({ status }: { status: AgentStatus }) {\n  const text = STATUS_TEXT[status];\n  return (\n    <span\n      data-slot=\"run-header-status\"\n      className={cn(\n        \"whitespace-nowrap\",\n        status === \"waiting\" && \"text-kura-warning-text\",\n        status === \"failed\" && \"text-kura-danger-text\",\n        (status === \"done\" || status === \"paused\" || status === \"running\") && \"text-kura-fg-muted\",\n      )}\n    >\n      {status === \"running\" ? <ShimmerText>{text}</ShimmerText> : text}\n    </span>\n  );\n}\n\n/** Segmented bar for discrete steps (continuous above 24 steps). */\nfunction StepsBar({\n  progress,\n  status,\n  className,\n}: {\n  progress: RunHeaderProgress;\n  status: AgentStatus;\n  className?: string;\n}) {\n  const total = Math.max(0, progress.total);\n  const done = Math.min(total, Math.max(0, progress.done));\n  const live = status === \"running\" || status === \"waiting\";\n  const currentTone =\n    status === \"failed\"\n      ? \"bg-kura-danger\"\n      : status === \"waiting\"\n        ? \"bg-kura-warning\"\n        : status === \"running\"\n          ? \"bg-kura-accent\"\n          : \"bg-kura-hover\";\n  if (total > 24) {\n    return (\n      <span\n        aria-hidden\n        className={cn(\"flex h-1 overflow-hidden rounded-full bg-kura-hover\", className)}\n      >\n        <span\n          className=\"h-full rounded-full bg-kura-fg transition-[width] duration-(--kura-duration-slow) ease-kura-out motion-reduce:transition-none\"\n          style={{ width: `${(done / total) * 100}%` }}\n        />\n      </span>\n    );\n  }\n  return (\n    <span aria-hidden className={cn(\"flex h-1 gap-[3px]\", className)}>\n      {Array.from({ length: total }, (_, i) => (\n        <span\n          key={i}\n          className={cn(\n            \"h-full min-w-0 flex-1 rounded-full transition-colors duration-(--kura-duration-base) ease-kura-out motion-reduce:transition-none\",\n            i < done\n              ? \"bg-kura-fg\"\n              : i === done && (live || status === \"failed\")\n                ? currentTone\n                : \"bg-kura-hover\",\n            i === done && status === \"running\" && \"animate-kura-pulse motion-reduce:animate-none\",\n          )}\n        />\n      ))}\n    </span>\n  );\n}\n\nfunction budgetTone(cost: number, budget: number) {\n  const r = cost / budget;\n  if (r > 1) return \"bg-kura-danger\";\n  if (r >= 0.8) return \"bg-kura-warning\";\n  return \"bg-kura-fg\";\n}\n\nfunction Meter({ value, tone, className }: { value: number; tone: string; className?: string }) {\n  return (\n    <span\n      aria-hidden\n      className={cn(\"flex h-1 overflow-hidden rounded-full bg-kura-hover\", className)}\n    >\n      <span\n        className={cn(\n          \"h-full rounded-full transition-[width,background-color] duration-(--kura-duration-slow) ease-kura-out motion-reduce:transition-none\",\n          tone,\n        )}\n        style={{ width: `${Math.min(1, Math.max(0, value)) * 100}%` }}\n      />\n    </span>\n  );\n}\n\n/**\n * The header of one agent run: title, agent and model, status, branch, a live elapsed clock,\n * tokens, cost against a budget, plan progress, and Stop / Pause / Resume controls with\n * keyboard shortcuts. `bar` is one dense row; `panel` adds a stats strip.\n */\nexport function RunHeader({\n  title,\n  agent,\n  model,\n  status,\n  startedAt,\n  endedAt,\n  tokens,\n  cost,\n  budget,\n  branch,\n  progress,\n  actions,\n  onStop,\n  onPause,\n  onResume,\n  variant = \"panel\",\n  shortcuts = true,\n  className,\n  ...props\n}: RunHeaderProps) {\n  const id = React.useId();\n  const live = LIVE.includes(status);\n  const ticking = useElapsed(startedAt, live && endedAt == null);\n  const elapsed =\n    startedAt != null && endedAt != null\n      ? Math.max(0, +new Date(endedAt) - +new Date(startedAt))\n      : ticking;\n  const clock = elapsed == null ? \"–:––\" : formatClock(elapsed);\n\n  const canStop = live && onStop != null;\n  const canPause = status === \"running\" && onPause != null;\n  const canResume = status === \"paused\" && onResume != null;\n\n  useHotkey(STOP_KEYS, () => onStop?.(), { enabled: shortcuts && canStop });\n  useHotkey(PAUSE_KEYS, () => (canPause ? onPause?.() : onResume?.()), {\n    enabled: shortcuts && (canPause || canResume),\n  });\n\n  const bar = variant === \"bar\";\n  const totalTokens = tokens ? tokens.input + tokens.output : null;\n  const over = cost != null && budget != null && budget > 0 && cost > budget;\n\n  const pauseButton = (canPause || canResume) && (\n    <IconButton\n      data-slot=\"run-header-pause\"\n      size=\"sm\"\n      variant={bar ? \"ghost\" : \"outline\"}\n      label={canPause ? \"Pause run\" : \"Resume run\"}\n      shortcut={shortcuts ? PAUSE_KEYS : undefined}\n      aria-keyshortcuts={shortcuts ? \"Meta+Shift+P Control+Shift+P\" : undefined}\n      icon={canPause ? <Pause className=\"fill-current\" /> : <Play className=\"fill-current\" />}\n      onClick={() => (canPause ? onPause?.() : onResume?.())}\n    />\n  );\n\n  const stopIcon = <Square className=\"size-3! fill-current\" />;\n  const stopButton =\n    canStop &&\n    (bar ? (\n      <IconButton\n        data-slot=\"run-header-stop\"\n        size=\"sm\"\n        label=\"Stop run\"\n        shortcut={shortcuts ? STOP_KEYS : undefined}\n        aria-keyshortcuts={shortcuts ? \"Meta+. Control+.\" : undefined}\n        icon={stopIcon}\n        onClick={onStop}\n      />\n    ) : (\n      <Tooltip content=\"Stop run\" shortcut={shortcuts ? STOP_KEYS : undefined}>\n        <Button\n          data-slot=\"run-header-stop\"\n          variant=\"outline\"\n          size=\"sm\"\n          onClick={onStop}\n          aria-keyshortcuts={shortcuts ? \"Meta+. Control+.\" : undefined}\n          aria-label=\"Stop run\"\n          className=\"max-@md:size-7 max-@md:px-0\"\n        >\n          {stopIcon}\n          <span className=\"hidden @md:inline\">Stop</span>\n        </Button>\n      </Tooltip>\n    ));\n\n  const controls =\n    actions != null || pauseButton || stopButton ? (\n      <div data-slot=\"run-header-controls\" className=\"flex shrink-0 items-center gap-1.5\">\n        {actions}\n        {pauseButton}\n        {stopButton}\n      </div>\n    ) : null;\n\n  const announce = (\n    <span role=\"status\" className=\"sr-only\">\n      {`Run ${STATUS_TEXT[status].toLowerCase()}`}\n    </span>\n  );\n\n  const branchChip = branch != null && (\n    <span\n      data-slot=\"run-header-branch\"\n      className=\"inline-flex min-w-0 items-center gap-1 font-kura-mono text-[12px] text-kura-fg-muted\"\n    >\n      <GitBranch aria-hidden className=\"size-3.5 shrink-0 text-kura-fg-subtle\" strokeWidth={1.75} />\n      <span className=\"truncate\">{branch}</span>\n    </span>\n  );\n\n  if (bar) {\n    return (\n      <section\n        aria-labelledby={`${id}-title`}\n        data-slot=\"run-header\"\n        data-variant=\"bar\"\n        data-status={status}\n        className={cn(\n          \"@container flex h-11 min-w-0 items-center gap-3 rounded-kura-xl bg-kura-surface pr-2 pl-3.5 font-kura-sans text-[13px] text-kura-fg shadow-kura-sm\",\n          className,\n        )}\n        {...props}\n      >\n        <StatusGlyph status={status} label={null} />\n        <div className=\"flex min-w-0 flex-1 items-center gap-2.5\">\n          <h2\n            id={`${id}-title`}\n            data-slot=\"run-header-title\"\n            className=\"min-w-0 truncate font-medium tracking-[-0.006em]\"\n          >\n            {title}\n          </h2>\n          <span className=\"hidden text-[12px] @md:inline\">\n            <StatusLabel status={status} />\n          </span>\n          {branchChip && <span className=\"hidden min-w-0 @3xl:flex\">{branchChip}</span>}\n        </div>\n        <dl\n          data-slot=\"run-header-stats\"\n          className=\"flex shrink-0 items-center gap-4 font-kura-mono text-[12px] text-kura-fg-muted tabular-nums\"\n        >\n          {progress && progress.total > 0 && (\n            <div className=\"hidden items-center gap-2 @lg:flex\">\n              <dt className=\"sr-only\">Steps</dt>\n              <StepsBar progress={progress} status={status} className=\"w-20\" />\n              <dd>\n                {Math.min(progress.done, progress.total)}\n                <span className=\"text-kura-fg-subtle\">/{progress.total}</span>\n              </dd>\n            </div>\n          )}\n          {totalTokens != null && (\n            <div className=\"hidden @2xl:block\">\n              <dt className=\"sr-only\">Tokens</dt>\n              <dd className=\"inline-block min-w-[5ch] text-right\">\n                {formatTokens(totalTokens)}\n                <span className=\"font-kura-sans text-kura-fg-subtle\"> tokens</span>\n              </dd>\n            </div>\n          )}\n          {cost != null && (\n            <div className=\"hidden @xl:block\">\n              <dt className=\"sr-only\">Cost</dt>\n              <dd className={cn(over && \"text-kura-danger-text\")}>\n                {formatCost(cost)}\n                {budget != null && (\n                  <span className=\"text-kura-fg-subtle\">\n                    <span className=\"font-kura-sans\"> of </span>\n                    {formatCost(budget)}\n                  </span>\n                )}\n              </dd>\n            </div>\n          )}\n          <div>\n            <dt className=\"sr-only\">Elapsed</dt>\n            <dd\n              className=\"inline-block min-w-[5ch] text-right text-kura-fg\"\n              data-slot=\"run-header-elapsed\"\n            >\n              {clock}\n            </dd>\n          </div>\n        </dl>\n        {controls && (\n          <>\n            <span aria-hidden className=\"h-4 w-px shrink-0 bg-kura-border\" />\n            {controls}\n          </>\n        )}\n        {announce}\n      </section>\n    );\n  }\n\n  const stats: { key: string; label: string; node: React.ReactNode }[] = [];\n  stats.push({\n    key: \"elapsed\",\n    label: \"Elapsed\",\n    node: (\n      <span data-slot=\"run-header-elapsed\" className=\"text-kura-fg\">\n        {clock}\n      </span>\n    ),\n  });\n  if (progress && progress.total > 0) {\n    stats.push({\n      key: \"steps\",\n      label: \"Steps\",\n      node: (\n        <span className=\"flex flex-col gap-2\">\n          <span>\n            <span className=\"text-kura-fg\">{Math.min(progress.done, progress.total)}</span>\n            <span className=\"text-kura-fg-subtle\">\n              <span className=\"font-kura-sans\"> of </span>\n              {progress.total}\n            </span>\n          </span>\n          <StepsBar progress={progress} status={status} />\n        </span>\n      ),\n    });\n  }\n  if (tokens) {\n    stats.push({\n      key: \"tokens\",\n      label: \"Tokens\",\n      node: (\n        <span className=\"truncate\">\n          <span className=\"text-kura-fg\">{formatTokens(tokens.input)}</span>\n          <span className=\"font-kura-sans text-kura-fg-subtle\"> in · </span>\n          <span className=\"text-kura-fg\">{formatTokens(tokens.output)}</span>\n          <span className=\"font-kura-sans text-kura-fg-subtle\"> out</span>\n        </span>\n      ),\n    });\n  }\n  if (cost != null) {\n    stats.push({\n      key: \"cost\",\n      label: budget != null ? \"Spend\" : \"Cost\",\n      node: (\n        <span className=\"flex flex-col gap-2\">\n          <span>\n            <span className={over ? \"text-kura-danger-text\" : \"text-kura-fg\"}>\n              {formatCost(cost)}\n            </span>\n            {budget != null && (\n              <span className=\"text-kura-fg-subtle\">\n                <span className=\"font-kura-sans\"> of </span>\n                {formatCost(budget)}\n              </span>\n            )}\n          </span>\n          {budget != null && budget > 0 && (\n            <Meter value={cost / budget} tone={budgetTone(cost, budget)} />\n          )}\n        </span>\n      ),\n    });\n  }\n\n  // Each meta item carries its own separator so items can drop out at narrow widths without\n  // leaving a dangling \"·\". Branch outlives model, which outlives the agent name.\n  const meta: { key: string; className: string; node: React.ReactNode }[] = [];\n  if (agent != null)\n    meta.push({\n      key: \"agent\",\n      className: \"hidden @md:flex\",\n      node: (\n        <span data-slot=\"run-header-agent\" className=\"text-kura-fg-muted\">\n          {agent}\n        </span>\n      ),\n    });\n  if (model != null)\n    meta.push({\n      key: \"model\",\n      className: \"hidden @sm:flex\",\n      node: (\n        <span data-slot=\"run-header-model\" className=\"truncate font-kura-mono\">\n          {model}\n        </span>\n      ),\n    });\n  if (branchChip) meta.push({ key: \"branch\", className: \"flex\", node: branchChip });\n\n  return (\n    <section\n      aria-labelledby={`${id}-title`}\n      data-slot=\"run-header\"\n      data-variant=\"panel\"\n      data-status={status}\n      className={cn(\n        \"@container flex min-w-0 flex-col overflow-hidden rounded-kura-xl bg-kura-surface font-kura-sans text-kura-fg shadow-kura-sm\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex items-start gap-3 px-4 pt-3.5 pb-3.5\">\n        <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n          <h2\n            id={`${id}-title`}\n            data-slot=\"run-header-title\"\n            className=\"truncate font-medium text-[15px] leading-5 tracking-[-0.011em]\"\n          >\n            {title}\n          </h2>\n          <div className=\"flex min-w-0 items-center gap-2 text-[12px] text-kura-fg-subtle leading-4\">\n            <span className=\"inline-flex shrink-0 items-center gap-1.5\">\n              <StatusGlyph status={status} size=\"sm\" label={null} />\n              <StatusLabel status={status} />\n            </span>\n            {meta.map((m) => (\n              <span key={m.key} className={cn(\"min-w-0 items-center gap-2\", m.className)}>\n                <span aria-hidden className=\"text-kura-border-strong\">\n                  ·\n                </span>\n                {m.node}\n              </span>\n            ))}\n          </div>\n        </div>\n        {controls}\n      </div>\n      <dl\n        data-slot=\"run-header-stats\"\n        className=\"grid grid-cols-2 gap-px @xl:grid-cols-[repeat(var(--run-header-stats),minmax(0,1fr))]\"\n        style={{ [\"--run-header-stats\" as string]: stats.length }}\n      >\n        {stats.map((s) => (\n          <div\n            key={s.key}\n            data-slot=\"run-header-stat\"\n            data-stat={s.key}\n            className=\"flex min-w-0 flex-col gap-1 bg-kura-surface px-4 pt-2.5 pb-3 shadow-[-1px_-1px_0_0_var(--kura-border-subtle)]\"\n          >\n            <dt className=\"text-[12px] text-kura-fg-subtle leading-4\">{s.label}</dt>\n            <dd className=\"min-w-0 font-kura-mono text-[13px] leading-5 tabular-nums\">{s.node}</dd>\n          </div>\n        ))}\n      </dl>\n      {announce}\n    </section>\n  );\n}\n"
    }
  ],
  "categories": [
    "agent",
    "agent",
    "run",
    "session",
    "header"
  ],
  "docs": "Docs: https://kuro.abstergo.fyi/components/run-header · Markdown for agents: https://kuro.abstergo.fyi/components/run-header.md",
  "meta": {
    "kura": {
      "status": "experimental",
      "whenToUse": [
        "The top of a page or pane that shows one agent session (Claude Code, Codex, Devin-style runs): what it is doing, how long, how much it costs, and a way to stop it.",
        "A dense status row above a run's transcript, plan or fleet (variant='bar').",
        "Showing spend against a per-run budget, with the meter turning amber at 80% and red past 100%.",
        "Giving the human Stop / Pause / Resume with keyboard shortcuts while an agent works."
      ],
      "whenNotToUse": [
        "The run's step-by-step plan — use task-list (todo-style) or agent-timeline (steps with connectors).",
        "Parallel subagents inside a run — use agent-fleet.",
        "Context-window usage of one conversation — use context-meter.",
        "A single tool invocation — use tool-call."
      ]
    }
  }
}
