{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "agent-state",
  "title": "Agent state",
  "description": "Where an agent is, in one quiet line: a ring, a breath, a round, the accent disc or an ink disc, and the word for it.",
  "registryDependencies": [
    "https://thedirectors.agency/ui/r/0nlytype.json",
    "https://thedirectors.agency/ui/r/marker.json",
    "https://thedirectors.agency/ui/r/spinner.json"
  ],
  "files": [
    {
      "path": "registry/0nlytype/ui/agent-state.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Marker } from \"@/components/ui/marker\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\ntype AgentStateValue = \"ready\" | \"thinking\" | \"working\" | \"input\" | \"done\"\n\n/** What each state says when no other words are given. */\nconst agentStateWords: Record<AgentStateValue, string> = {\n  ready: \"Ready\",\n  thinking: \"Thinking\",\n  working: \"Working\",\n  input: \"Needs your input\",\n  done: \"Done\",\n}\n\ntype AgentStateProps = Omit<React.ComponentProps<\"p\">, \"children\"> & {\n  /**\n   * Where the agent is. ready: a hairline ring, nothing started. thinking: the ring breathes. working: three\n   * voices go round it. input: the one accent disc, your turn. done: the disc filled in ink, the word in pencil.\n   * Only thinking and working move, and only while the caller says so.\n   */\n  state: AgentStateValue\n  /** dot (the default): the mark before the word, after the calendar's discs. word: no mark; the word carries it. */\n  variant?: \"dot\" | \"word\"\n  /** Other words for the state (\"Reading the brief\"). Leave it out for the state's own word. */\n  children?: string\n}\n\n/** Where an agent is, in one quiet line: a mark and a word. It moves only while the agent thinks or works. */\nfunction AgentState({ state, variant = \"dot\", children, className, ...props }: AgentStateProps) {\n  const word = children || agentStateWords[state]\n  const busy = state === \"thinking\" || state === \"working\"\n  // The mark lands only when the state changes, never on first paint.\n  const [seen, setSeen] = React.useState(state)\n  const [turned, setTurned] = React.useState(false)\n  if (seen !== state) {\n    setSeen(state)\n    setTurned(true)\n  }\n  return (\n    <Marker\n      data-variant={variant === \"dot\" ? undefined : variant}\n      data-state={state}\n      data-turned={turned || undefined}\n      role=\"status\"\n      aria-live=\"polite\"\n      aria-busy={busy || undefined}\n      className={cn(\"ot-agent\", className)}\n      {...props}\n    >\n      {variant === \"dot\" ? (\n        <span key={state} className=\"ot-agent-mark\" aria-hidden=\"true\">\n          {state === \"thinking\" ? <Spinner variant=\"breath\" /> : state === \"working\" ? <Spinner variant=\"round\" /> : null}\n        </span>\n      ) : null}\n      {variant === \"word\" && busy ? (\n        <>\n          <span className=\"ot-sr\">{word}</span>\n          {/* The word spinner is always a status; here the line is, so its own role and voice are taken off. */}\n          <Spinner variant=\"word\" label={word} role={undefined} aria-live={undefined} aria-hidden=\"true\" />\n        </>\n      ) : (\n        <span className=\"ot-agent-word\">{word}</span>\n      )}\n    </Marker>\n  )\n}\n\nexport { AgentState, agentStateWords, type AgentStateProps, type AgentStateValue }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/0nlytype/styles/agent-state.css",
      "content": "@layer components {\n  /* ── ot-agent ── where an agent is, as a marker's status line: a mark and a word. The mark lives the life of\n     the \"28 December\" calendar's discs: a hairline ring is to come, the one accent disc is now (your turn), a\n     filled ink disc is gone by. Only thinking and working move, as the spinner does, while the caller says so. */\n  .ot-agent { gap: var(--ot-space-2); color: var(--ot-graphite); }\n  .ot-agent:is([data-state=\"thinking\"], [data-state=\"working\"], [data-state=\"input\"]) { color: var(--ot-ink); }\n  .ot-agent[data-state=\"done\"] { color: var(--ot-pencil); }\n\n  /* The mark stands in a box a capital tall, as the spinner's art does, so the ring and the discs read as one more glyph. */\n  .ot-agent-mark { position: relative; display: inline-grid; place-items: center; flex: none; inline-size: 0.7em; block-size: 0.7em; }\n  .ot-agent-mark > .ot-dots { display: block; line-height: 0; }\n  .ot-agent-mark:empty::before { content: \"\"; inline-size: 0.5em; block-size: 0.5em; box-sizing: border-box; border-radius: 50%; }\n  .ot-agent[data-state=\"ready\"] .ot-agent-mark::before { border: var(--ot-hairline) solid var(--ot-rule-strong); }\n  .ot-agent[data-state=\"input\"] .ot-agent-mark::before { background: var(--ot-accent); }\n  .ot-agent[data-state=\"done\"] .ot-agent-mark::before { background: var(--ot-ink); }\n  /* What's inked lands: a new disc or ring arrives with one small rebound, only after a change. */\n  .ot-agent[data-turned] .ot-agent-mark::before { animation: ot-agent-land var(--ot-moderato) var(--ot-spiccato) backwards; }\n  .ot-agent[data-turned] .ot-agent-mark > .ot-dots { animation: ot-arrive var(--ot-moderato) var(--ot-exhale) backwards; }\n  @keyframes ot-agent-land { from { scale: 0; } }\n\n  /* word: no mark. The word carries the state: inked letter by letter while busy, the accent when it's your turn. */\n  .ot-agent[data-variant=\"word\"][data-state=\"input\"] { color: var(--ot-accent); }\n\n  @media (forced-colors: active) {\n    .ot-agent-mark::before { forced-color-adjust: none; }\n    .ot-agent[data-state=\"ready\"] .ot-agent-mark::before { border-color: CanvasText; }\n    .ot-agent[data-state=\"input\"] .ot-agent-mark::before { background: Highlight; }\n    .ot-agent[data-state=\"done\"] .ot-agent-mark::before { background: CanvasText; }\n    .ot-agent[data-variant=\"word\"][data-state=\"input\"] { color: Highlight; }\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/0nlytype/agent-state.css"
    }
  ],
  "css": {
    "@import \"../styles/0nlytype/agent-state.css\"": {}
  },
  "meta": {
    "movement": "XI",
    "underneath": "native",
    "contract": "ot-agent"
  },
  "type": "registry:ui"
}
