{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tool-call",
  "type": "registry:ui",
  "title": "Tool call",
  "description": "What an agent did, one row per call: a status mark (a still half circle while it runs, never a spinner), the action in words, the target in mono cut at the end, what it found (\"5 sources\", \"+2 −2\") and the time. A call with an input or an output opens them under it, in a mono well; a part with its own surface (a log, a diff, a list of sources) opens as given in details. A group folds a run of calls and closes itself when they finish, and an approval asks first on a raised panel with Allow as the accent fill and Deny beside it, then becomes a row that says what was chosen.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://littmd.vercel.app/r/litt.json",
    "https://littmd.vercel.app/r/button.json",
    "https://littmd.vercel.app/r/checkbox.json",
    "https://littmd.vercel.app/r/icons.json",
    "https://littmd.vercel.app/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/litt/ui/tool-call.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/tool-call 2026.11.06. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Tool call: what an agent did, one row per call, a group that folds a run of them, and an approval that asks first\n * (components.md, \"Tool call\"; proposals/agents.md, decisions 4, 7 and 9).\n *\n * - Row: at least 32px tall (44px on a coarse pointer), in one line: the 16px StatusIcon, the action in words (small,\n *   fg-muted: \"Read\", \"Ran\", \"Searched\"), the target in mono (small, fg: \"src/app.tsx\", \"npm test\"), cut at the end with\n *   an ellipsis, then a word for the status when it has one and the time (mono caption, fg-subtle, tabular) at the row's\n *   end. A 16px slot at the very end holds the chevron, and stays empty on a call with no details, so the times of a\n *   run of calls line up.\n * - Status: the Timeline's marks through StatusIcon. Running is the half-filled circle in fg and never spins or pulses.\n *   Done is the check in fg-muted. Failed is the x in danger, with \"Failed\" in words. Waiting is the empty circle in\n *   fg-subtle with \"Waiting for approval\". Denied is the dashed circle with \"Denied\". Color is on the icon only, and every\n *   status is also said in hidden text: a done or running call has a hidden word, the others say it in the visible one.\n * - Details: with `input`, `output` or `details` the row is a disclosure button with a chevron that turns a quarter on the\n *   instant tier. Input and output are captioned blocks (the caption in fg-subtle) in mono at the caption size in fg on\n *   surface-2, with an inset line at the surface radius, padded space-3. Past 16rem a block scrolls inside, and it is\n *   focusable and labelled (\"Output of npm test\"). Output stays fg when the call failed: the status says it. `details` is\n *   drawn as given after them, for a part with its own surface and scroller (a LogView, a SourceList, a FileChanges), so it\n *   is never a well inside a well.\n * - Result: a few words on what the call found or changed (\"5 sources\", \"+2 −2\") sit after the target at the small size in\n *   fg-subtle, and never shrink, so a folded group still reads what each call did. The target is cut first.\n * - Group: ToolCallGroup folds a run of calls under one row (the caller's summary, or \"4 tool calls\"). It is open while\n *   `running`, and closes itself when `running` goes false unless the reader toggled it. Its calls are a list.\n * - Approval: ToolApproval is a litt-raised panel in the flow (never glass, R3): a title (body, the title weight, fg), the command in mono on\n *   surface-2 at the control radius, one line in fg-subtle, an optional \"always allow\" Checkbox, then md actions, Allow\n *   (primary, the view's one accent fill because the Composer's Send is disabled while it waits) and Deny (ghost). It does\n *   not take focus when it appears: the person may be reading. A polite live region says \"Waiting for your approval:\n *   <command>\". With a `decision` it becomes a ToolCall row. If the buttons held focus when the choice was made, focus\n *   moves to the row, so a keyboard never falls back to the top of the page.\n * - Flat: a call and a group are grey cards at the surface radius with no frame, details sit in white wells, and the\n *   approval is a --litt-surface-1 card with the float shadow and no hairline.\n * - Motion: the chevron's quarter turn is the instant tier (CSS). A call's details and a group's calls fade in and travel a\n *   quarter of the travel token on the responsive tier, and leave on the exit token. Their height snaps. A status mark\n *   swaps at once and colors fade on the instant tier. Nothing loops.\n * - Phones: rows grow to 44px on a coarse pointer, a long target is cut with an ellipsis, the command wraps instead of\n *   scrolling, and the actions wrap. A pressed row takes the hover color on a coarse pointer.\n */\nimport { Children, createContext, isValidElement, useContext, useEffect, useId, useRef, useState, type ReactNode } from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Button } from \"./button\";\nimport { Checkbox } from \"./checkbox\";\nimport { ChevronRightIcon, StatusIcon, type StepStatus } from \"./icons\";\nimport { useExit, useTransition, useTravel } from \"../../lib/litt/motion\";\n\n/* ----------------------------------------------------------------- lookups */\n\n/** Where a call stands. Waiting shows the empty circle and denied the dashed one. */\nexport type ToolCallStatus = \"running\" | \"done\" | \"failed\" | \"waiting\" | \"denied\";\n\n/** What the person decided about an approval. Pending shows the panel; the other two show a row. */\nexport type ToolApprovalDecision = \"pending\" | \"allowed\" | \"denied\";\n\n/** The mark each status draws. Literal strings, so Tailwind sees every class. */\nconst MARK: Record<ToolCallStatus, StepStatus> = { running: \"running\", done: \"done\", failed: \"failed\", waiting: \"pending\", denied: \"skipped\" };\nconst TONE: Record<ToolCallStatus, string> = {\n  running: \"text-litt-fg\",\n  done: \"text-litt-fg-muted\",\n  failed: \"text-litt-danger\",\n  waiting: \"text-litt-fg-subtle\",\n  denied: \"text-litt-fg-subtle\",\n};\n/** The visible word a status carries. Done and running have none: the mark and the hidden word say it. */\nconst WORD: Partial<Record<ToolCallStatus, string>> = { failed: \"Failed\", waiting: \"Waiting for approval\", denied: \"Denied\" };\nconst HIDDEN_WORD: Partial<Record<ToolCallStatus, string>> = { running: \"running\", done: \"done\" };\n\n/* ----------------------------------------------------------------- time */\n\nconst tidy = (n: number) => (Number.isInteger(n) ? String(n) : n < 10 ? n.toFixed(1) : String(Math.round(n)));\n\n/** \"0.2s\", \"14s\", \"1m 12s\". */\nfunction formatSeconds(total: number): string {\n  const s = Math.max(0, total);\n  if ((s >= 10 ? Math.round(s) : s) < 60) return `${tidy(s)}s`;\n  const whole = Math.round(s);\n  const m = Math.floor(whole / 60);\n  const r = whole - m * 60;\n  return r ? `${m}m ${r}s` : `${m}m`;\n}\n\n/** \"14 seconds\": what a screen reader should say where the page shows \"14s\". */\nfunction speakSeconds(total: number): string {\n  const s = Math.max(0, total);\n  if ((s >= 10 ? Math.round(s) : s) < 60) {\n    const t = tidy(s);\n    return `${t} ${t === \"1\" ? \"second\" : \"seconds\"}`;\n  }\n  const whole = Math.round(s);\n  const m = Math.floor(whole / 60);\n  const r = whole - m * 60;\n  const minutes = `${m} ${m === 1 ? \"minute\" : \"minutes\"}`;\n  return r ? `${minutes} ${r} ${r === 1 ? \"second\" : \"seconds\"}` : minutes;\n}\n\n/** A number is seconds and is formatted and spoken. A string is shown and spoken as it is. */\nfunction readTime(value: number | string) {\n  return typeof value === \"number\" ? { visual: formatSeconds(value), spoken: speakSeconds(value) } : { visual: value, spoken: value };\n}\n\nconst given = (node: ReactNode) => node !== undefined && node !== null && node !== false && node !== \"\";\n\n/* ----------------------------------------------------------------- shared pieces */\n\n/** True inside a ToolCallGroup, where a call is a list item. */\nconst InGroup = createContext(false);\n\n/* Flat: a call (or a run of them) is a grey card with no frame, rows part by space (flat-pill.md, rules 4 and 5). */\nconst CARD =\n  \"in-[[data-material=flat]]:rounded-litt-surface in-[[data-material=flat]]:bg-litt-surface-3 in-[[data-material=flat]]:px-4 in-[[data-material=flat]]:py-1 \" +\n  \"in-[[data-material=flat]]:[[data-tool-call]+&]:mt-1 in-[[data-material=flat]]:forced-colors:border \";\n\nconst ROW =\n  \"group/row flex min-h-8 w-full items-center gap-2 text-left pointer-coarse:min-h-11\";\nconst ROW_BUTTON =\n  \"cursor-pointer rounded-litt-control focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-litt-focus \" +\n  \"[-webkit-tap-highlight-color:transparent]\";\n/* Only a row that opens something brightens: a plain row has nothing to answer. A press does the same on a finger. */\nconst BRIGHTEN =\n  \"transition-colors duration-(--litt-dur-instant) ease-litt group-hover/row:text-litt-fg pointer-coarse:group-active/row:text-litt-fg\";\n\n/** The chevron's slot, always 16px wide at the row's end so the times line up. It turns a quarter when open. */\nfunction Chevron({ show, open }: { show: boolean; open: boolean }) {\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={\n        \"grid size-4 shrink-0 place-items-center text-litt-fg-subtle transition-colors duration-(--litt-dur-instant) ease-litt \" +\n        \"group-hover/row:text-litt-fg-muted pointer-coarse:group-active/row:text-litt-fg-muted\"\n      }\n    >\n      {show && (\n        <ChevronRightIcon className={\"transition-[rotate] duration-(--litt-dur-instant) ease-litt \" + (open ? \"rotate-90\" : \"rotate-0\")} />\n      )}\n    </span>\n  );\n}\n\n/** A time at the end of a row: mono caption in fg-subtle. The short form is hidden from screen readers, the spoken form is not. */\nfunction Time({ value }: { value: number | string }) {\n  const t = readTime(value);\n  return (\n    <span className=\"text-litt-caption font-normal tabular-nums whitespace-nowrap text-litt-fg-subtle\">\n      <span aria-hidden=\"true\">{t.visual}</span>\n      <span className=\"sr-only\"> {t.spoken}</span>\n    </span>\n  );\n}\n\n/** The body of a disclosure: it fades in and travels a quarter of the travel token on the responsive tier, and leaves on the exit token. Its height snaps. */\nfunction Reveal({ open, id, className = \"\", children }: { open: boolean; id: string; className?: string; children: ReactNode }) {\n  const enter = useTransition(\"responsive\");\n  const exit = useExit(\"responsive\");\n  const travel = useTravel();\n  /* A body that is open on the first render appears without a fade. Not through AnimatePresence initial={false}:\n     that reaches every motion component inside the body and swallows their own entrances (see tabs.tsx). */\n  const mounted = useRef(false);\n  useEffect(() => {\n    mounted.current = true;\n  }, []);\n  return (\n    <AnimatePresence>\n      {open && (\n        <motion.div\n          key=\"body\"\n          id={id}\n          className={className}\n          initial={mounted.current ? { opacity: 0, y: -travel / 4 } : false}\n          animate={{ opacity: 1, y: 0, transition: enter }}\n          exit={{ opacity: 0, transition: exit }}\n        >\n          {children}\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n\n/** Open and closed, controlled or not. */\nfunction useDisclosure(openProp: boolean | undefined, defaultOpen: boolean, onOpenChange: ((open: boolean) => void) | undefined) {\n  const [inner, setInner] = useState(defaultOpen);\n  const open = openProp ?? inner;\n  const set = (next: boolean) => {\n    if (openProp === undefined) setInner(next);\n    onOpenChange?.(next);\n  };\n  return [open, set] as const;\n}\n\n/** A captioned block of mono text: a surface-2 well with an inset line that scrolls inside past 16rem and can be focused. */\nfunction Block({ caption, name, children }: { caption: string; name: string | undefined; children: ReactNode }) {\n  return (\n    <div className=\"grid min-w-0 gap-1\">\n      <p aria-hidden=\"true\" className=\"text-litt-caption text-litt-fg-subtle\">\n        {caption}\n      </p>\n      <div\n        role=\"region\"\n        tabIndex={0}\n        aria-label={name ? `${caption} of ${name}` : caption}\n        className={\n          \"max-h-64 overflow-auto rounded-[calc(var(--litt-radius-surface)-4px)] bg-litt-surface-2 p-3 font-litt-mono text-litt-caption font-normal text-litt-fg \" +\n          \"whitespace-pre-wrap [overflow-wrap:anywhere] shadow-[inset_0_0_0_1px_var(--litt-line)] \" +\n          \"in-[[data-material=flat]]:bg-litt-surface-1 in-[[data-material=flat]]:shadow-none \" +\n          \"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-litt-focus\"\n        }\n      >\n        {children}\n      </div>\n    </div>\n  );\n}\n\n/* ----------------------------------------------------------------- ToolCall */\n\nexport type ToolCallProps = {\n  /** Where the call stands. Waiting shows the empty circle and \"Waiting for approval\", denied the dashed circle and \"Denied\", failed the x and \"Failed\". */\n  status?: ToolCallStatus;\n  /** What the call did, in words: \"Read\", \"Ran\", \"Searched\". Use the present tense while it runs: \"Running\". */\n  action: ReactNode;\n  /** What it did it to, in mono: a file, a command or a query. It is cut at the end with an ellipsis, and a text target is the full text in a tooltip. */\n  target?: ReactNode;\n  /** How long it took. A number is seconds (\"14s\", read as \"14 seconds\"); a string is shown and read as given. Leave it out while the time is not known. */\n  duration?: number | string;\n  /** What the call was given, shown under an \"Input\" caption when the row is opened. Text or elements. */\n  input?: ReactNode;\n  /** What the call returned, shown under an \"Output\" caption when the row is opened. Text or elements. It stays the foreground color when the call failed. */\n  output?: ReactNode;\n  /** A part with its own surface, drawn as given under the row when it is opened, after the input and output: a LogView for a command, a SourceList for a search, a FileChanges for an edit. */\n  details?: ReactNode;\n  /** A few words on what the call found or changed, after the target: \"5 sources\", \"+2 −2\", \"Exit 1\". It stays when the target is cut. */\n  result?: ReactNode;\n  /** Names the call in the details' labels (\"Output of npm test\"). Defaults to the target when that is text, then to the action when that is text. */\n  label?: string;\n  /** Replaces the status word after the target, and what a screen reader hears for the status: \"Allowed\" for an approved call. Failed, waiting and denied have their own words. */\n  statusLabel?: string;\n  /** Whether the details are open, when you control it. Pair it with onOpenChange. A call with no input, output or details does not open. */\n  open?: boolean;\n  /** Whether the details start open when you do not control them. */\n  defaultOpen?: boolean;\n  /** Called with the new state when the reader opens or closes the details. */\n  onOpenChange?: (open: boolean) => void;\n  /** Extra classes for the call, such as a top margin. */\n  className?: string;\n};\n\n/**\n * One tool call as a row: a status mark, the action in words, the target in mono, what it found and the time. With an input,\n * an output or details the row is a button that opens them under it. In a ToolCallGroup it is a list item.\n */\nexport function ToolCall({\n  status = \"done\",\n  action,\n  target,\n  duration,\n  input,\n  output,\n  details,\n  result,\n  label,\n  statusLabel,\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  className = \"\",\n}: ToolCallProps) {\n  const id = useId();\n  const inGroup = useContext(InGroup);\n  const [open, setOpen] = useDisclosure(openProp, defaultOpen, onOpenChange);\n\n  const hasInput = given(input);\n  const hasOutput = given(output);\n  const hasDetails = given(details);\n  const opens = hasInput || hasOutput || hasDetails;\n  const name = label ?? (typeof target === \"string\" ? target : typeof action === \"string\" ? action : undefined);\n  const word = statusLabel ?? WORD[status];\n\n  const content = (\n    <>\n      <StatusIcon status={MARK[status]} className={\"shrink-0 \" + TONE[status]} />\n      <span className={\"shrink-0 whitespace-nowrap text-litt-small text-litt-fg-muted \" + (opens ? BRIGHTEN : \"\")}>{action}</span>\n      {given(target) && (\n        <span title={typeof target === \"string\" ? target : undefined} className=\"min-w-0 truncate font-litt-mono text-litt-small text-litt-fg\">\n          {target}\n        </span>\n      )}\n      {given(result) && <span className=\"shrink-0 whitespace-nowrap text-litt-small text-litt-fg-subtle tabular-nums\">{result}</span>}\n      <span className=\"ml-auto flex shrink-0 items-center gap-2\">\n        {word ? (\n          <span className=\"whitespace-nowrap text-litt-small text-litt-fg-muted\">{word}</span>\n        ) : (\n          HIDDEN_WORD[status] && <span className=\"sr-only\">{HIDDEN_WORD[status]}</span>\n        )}\n        {duration !== undefined && <Time value={duration} />}\n        <Chevron show={opens} open={open} />\n      </span>\n    </>\n  );\n\n  return (\n    <div\n      role={inGroup ? \"listitem\" : undefined}\n      data-tool-call={inGroup ? undefined : \"\"}\n      className={\"min-w-0 \" + (inGroup ? \"\" : CARD) + className}\n    >\n      {opens ? (\n        <button\n          type=\"button\"\n          aria-expanded={open}\n          aria-controls={open ? id : undefined}\n          onClick={() => setOpen(!open)}\n          className={ROW + \" \" + ROW_BUTTON}\n        >\n          {content}\n        </button>\n      ) : (\n        <div className={ROW}>{content}</div>\n      )}\n      {opens && (\n        <Reveal open={open} id={id} className=\"grid gap-3 pt-1 pb-2 pl-6\">\n          {hasInput && (\n            <Block caption=\"Input\" name={name}>\n              {input}\n            </Block>\n          )}\n          {hasOutput && (\n            <Block caption=\"Output\" name={name}>\n              {output}\n            </Block>\n          )}\n          {hasDetails && <div className=\"min-w-0\">{details}</div>}\n        </Reveal>\n      )}\n    </div>\n  );\n}\n\n/* ----------------------------------------------------------------- ToolCallGroup */\n\nexport type ToolCallGroupProps = {\n  /** What the run did, in one line: \"Read 3 files and ran the tests\". Without one the row says \"4 tool calls\". */\n  summary?: ReactNode;\n  /** True while any call in the group runs. The group is open then, and closes itself when this goes false, unless the reader toggled it. */\n  running?: boolean;\n  /** How long the whole run took, at the end of the row. A number is seconds; a string is shown as given. */\n  duration?: number | string;\n  /** Whether the group is open, when you control it. Pair it with onOpenChange. This replaces the automatic open and close. */\n  open?: boolean;\n  /** Whether the group starts open or closed. It then stays as the reader leaves it, so it no longer follows `running`. */\n  defaultOpen?: boolean;\n  /** Called with the new state when the reader opens or closes the group. */\n  onOpenChange?: (open: boolean) => void;\n  /** The ToolCalls of the run. Keep an approval outside the group, so the panel is never hidden. */\n  children: ReactNode;\n  /** Extra classes for the group, such as a top margin. */\n  className?: string;\n};\n\n/**\n * Folds a run of tool calls under one row. It is open while `running` and closes itself when the run ends, unless the reader\n * opened or closed it first: their choice always wins. The calls inside are a list.\n */\nexport function ToolCallGroup({\n  summary,\n  running = false,\n  duration,\n  open: openProp,\n  defaultOpen,\n  onOpenChange,\n  children,\n  className = \"\",\n}: ToolCallGroupProps) {\n  const id = useId();\n  const summaryId = useId();\n  /* The reader's own choice, once they make one (or the caller's defaultOpen). Until then the group follows `running`. */\n  const [chosen, setChosen] = useState<boolean | null>(defaultOpen ?? null);\n  const open = openProp ?? chosen ?? running;\n  const toggle = () => {\n    const next = !open;\n    if (openProp === undefined) setChosen(next);\n    onOpenChange?.(next);\n  };\n  const count = Children.toArray(children).filter(isValidElement).length;\n  const said = summary ?? `${count} tool ${count === 1 ? \"call\" : \"calls\"}`;\n\n  return (\n    <div data-tool-call=\"\" className={\"min-w-0 \" + CARD + className}>\n      <button type=\"button\" aria-expanded={open} aria-controls={open ? id : undefined} onClick={toggle} className={ROW + \" \" + ROW_BUTTON}>\n        <span id={summaryId} className={\"min-w-0 py-1 text-litt-small text-litt-fg-muted \" + BRIGHTEN}>\n          {said}\n          {running && <span className=\"sr-only\"> running</span>}\n        </span>\n        <span className=\"ml-auto flex shrink-0 items-center gap-2\">\n          {duration !== undefined && <Time value={duration} />}\n          <Chevron show open={open} />\n        </span>\n      </button>\n      <Reveal open={open} id={id} className=\"pt-1 pb-2 pl-6\">\n        <InGroup.Provider value>\n          <div role=\"list\" aria-labelledby={summaryId}>\n            {children}\n          </div>\n        </InGroup.Provider>\n      </Reveal>\n    </div>\n  );\n}\n\n/* ----------------------------------------------------------------- ToolApproval */\n\nexport type ToolApprovalProps = {\n  /** The question, at the body size: \"Run a command?\". */\n  title?: ReactNode;\n  /** The call itself, in mono on a quiet well: \"npm run deploy\". It wraps rather than scrolls, so a phone can read all of it. */\n  command: string;\n  /** One line on what it touches, in the quiet tone: \"Publishes the Halo site to halo.studio.\" */\n  description?: ReactNode;\n  /** Called when the person presses Allow, with whether they ticked the always-allow box. Set `decision` to \"allowed\" in it. */\n  onAllow?: (details: { always: boolean }) => void;\n  /** Called when the person presses Deny. Set `decision` to \"denied\" in it. */\n  onDeny?: () => void;\n  /** Shows a checkbox with this label, such as \"Always allow npm run deploy\", above the actions. Leave it out for a plain Allow and Deny. */\n  alwaysAllowLabel?: string;\n  /** What the person decided. Pending shows the panel, and allowed or denied show the call as a row, \"Allowed\" or \"Denied\". */\n  decision?: ToolApprovalDecision;\n  /** Extra classes, such as a top margin. */\n  className?: string;\n};\n\n/**\n * Asks before a call runs: a raised panel in the flow with the command and Allow and Deny. It does not take focus when it\n * appears, and a polite live region says what it waits for. Once `decision` is set it becomes a tool call row.\n */\nexport function ToolApproval({\n  title = \"Run a command?\",\n  command,\n  description,\n  onAllow,\n  onDeny,\n  alwaysAllowLabel,\n  decision = \"pending\",\n  className = \"\",\n}: ToolApprovalProps) {\n  const titleId = useId();\n  const root = useRef<HTMLDivElement>(null);\n  const hadFocus = useRef(false);\n  const [always, setAlways] = useState(false);\n  const [said, setSaid] = useState(\"\");\n\n  /* The region exists before its words do, so a screen reader announces the change. After a choice made with focus in the\n     panel, focus moves to the row instead: the row is read when it takes focus, and the region stays quiet. */\n  useEffect(() => {\n    if (decision === \"pending\") {\n      setSaid(`Waiting for your approval: ${command}`);\n      return;\n    }\n    if (hadFocus.current) {\n      hadFocus.current = false;\n      setSaid(\"\");\n      root.current?.focus({ preventScroll: true });\n      return;\n    }\n    setSaid(`${decision === \"allowed\" ? \"Allowed\" : \"Denied\"}: ${command}`);\n  }, [decision, command]);\n\n  const decide = (choose: () => void) => {\n    hadFocus.current = root.current?.contains(document.activeElement) ?? false;\n    choose();\n  };\n\n  return (\n    <div\n      ref={root}\n      tabIndex={-1}\n      className={\"min-w-0 rounded-litt-control focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-litt-focus \" + className}\n    >\n      <div role=\"status\" className=\"sr-only\">\n        {said}\n      </div>\n      {decision === \"pending\" ? (\n        <div role=\"group\" aria-labelledby={titleId} className=\"litt-raised grid gap-3 p-4 in-[[data-material=flat]]:shadow-litt-float\">\n          <p id={titleId} className=\"text-litt-body font-litt-title text-litt-fg\">\n            {title}\n          </p>\n          <code className=\"block rounded-[calc(var(--litt-radius-surface)-4px)] bg-litt-surface-2 in-[[data-material=flat]]:bg-litt-surface-3 px-3 py-2 font-litt-mono text-litt-small text-litt-fg whitespace-pre-wrap [overflow-wrap:anywhere]\">\n            {command}\n          </code>\n          {given(description) && <p className=\"text-litt-small text-litt-fg-subtle\">{description}</p>}\n          {alwaysAllowLabel && <Checkbox label={alwaysAllowLabel} checked={always} onChange={(e) => setAlways(e.target.checked)} />}\n          <div className=\"flex flex-wrap items-center gap-3 pt-1\">\n            <Button variant=\"primary\" onClick={() => decide(() => onAllow?.({ always }))}>\n              Allow\n            </Button>\n            <Button variant=\"ghost\" onClick={() => decide(() => onDeny?.())}>\n              Deny\n            </Button>\n          </div>\n        </div>\n      ) : (\n        <ToolCall\n          status={decision === \"allowed\" ? \"done\" : \"denied\"}\n          statusLabel={decision === \"allowed\" ? \"Allowed\" : \"Denied\"}\n          action=\"Run\"\n          target={command}\n        />\n      )}\n    </div>\n  );\n}\n",
      "target": "components/litt/tool-call.tsx"
    }
  ],
  "docs": "Import ToolCall, ToolCallGroup and ToolApproval from components/litt/tool-call. Give a call a status, an action and a target, plus an input or an output to make it open. Put a LogView, a SourceList or a FileChanges in details, never in output, and say what the call found in result. Pass running to a group, and hold decision in state for an approval, setting it in onAllow and onDeny. Keep an approval outside a group, and disable your Send while it waits so the view holds one accent fill.",
  "meta": {
    "version": "2026.11.06",
    "released": "2026-11-06",
    "changelog": "https://littmd.vercel.app/changelog.md"
  }
}
