{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "opencode-sources",
  "title": "OpenCode Sources",
  "description": "Web search lines and a collapsible cited-sources list with an optional sequenced reveal.",
  "dependencies": [
    "cn"
  ],
  "registryDependencies": [
    "button",
    "collapsible"
  ],
  "files": [
    {
      "path": "registry/notra/opencode/components/opencode-sources.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\nimport type { CSSProperties } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\n\nimport { OPENCODE_SEARCH_STAGGER_MS } from \"../constants/opencode\";\nimport { useOpencodeReducedMotion } from \"../hooks/use-opencode-reduced-motion\";\nimport { useOpencodeSourcesSequence } from \"../hooks/use-opencode-sources-sequence\";\nimport type {\n  OpencodeSource,\n  OpencodeSourcesLabels,\n  OpencodeSourcesProps,\n} from \"../types/opencode\";\nimport { OpencodeActivity } from \"./opencode-activity\";\n\nconst TRAILING_MARKDOWN_PATTERN = /\\)\\*+$/u;\nconst NO_QUERIES: readonly string[] = [];\n\nconst REVEAL_CLASS =\n  \"opacity-100 transition-opacity duration-200 ease-out delay-(--opencode-delay) starting:opacity-0 motion-reduce:transition-none\";\n\nconst DEFAULT_LABELS: OpencodeSourcesLabels = {\n  citedSources: (count) =>\n    count === 1 ? \"1 cited source\" : `${count} cited sources`,\n  openSource: (title, domain) => `Open ${title} on ${domain}`,\n};\n\nconst cleanUrl = (url: string) => url.replace(TRAILING_MARKDOWN_PATTERN, \"\");\n\nconst safeHref = (url: string | undefined) => {\n  if (!url) {\n    return null;\n  }\n  try {\n    const parsed = new URL(cleanUrl(url));\n    return parsed.protocol === \"http:\" || parsed.protocol === \"https:\"\n      ? parsed.href\n      : null;\n  } catch {\n    return null;\n  }\n};\n\nconst SourceLine = ({\n  delayMs,\n  openLabel,\n  source,\n}: {\n  delayMs?: number;\n  openLabel: OpencodeSourcesLabels[\"openSource\"];\n  source: OpencodeSource;\n}) => {\n  const href = safeHref(source.url);\n  const style =\n    delayMs === undefined\n      ? undefined\n      : ({ \"--opencode-delay\": `${delayMs}ms` } as CSSProperties);\n  const content = (\n    <>\n      <span className=\"text-opencode-fg truncate\">{source.domain}</span>\n      <span className=\"text-opencode-muted truncate\">\n        {source.url ? cleanUrl(source.url) : source.title}\n      </span>\n    </>\n  );\n  const lineClass = cn(\n    \"grid grid-cols-[minmax(0,18ch)_minmax(0,1fr)] gap-[2ch]\",\n    delayMs !== undefined && REVEAL_CLASS\n  );\n\n  return (\n    <li className={lineClass} style={style}>\n      {href ? (\n        <a\n          aria-label={openLabel(source.title, source.domain)}\n          className=\"focus-visible:ring-opencode-blue col-span-2 grid grid-cols-subgrid outline-none hover:underline focus-visible:ring-1\"\n          href={href}\n          rel=\"noopener noreferrer\"\n          target=\"_blank\"\n        >\n          {content}\n        </a>\n      ) : (\n        content\n      )}\n    </li>\n  );\n};\n\nexport const OpencodeSources = ({\n  className,\n  defaultOpen = false,\n  labels = DEFAULT_LABELS,\n  queries = NO_QUERIES,\n  reducedMotion,\n  sequential = false,\n  sources,\n  ...props\n}: OpencodeSourcesProps) => {\n  const reduced = useOpencodeReducedMotion(reducedMotion);\n  const staged = sequential && !reduced;\n  const progress = useOpencodeSourcesSequence(\n    staged,\n    queries.length,\n    sources.length\n  );\n\n  if (sources.length === 0 && queries.length === 0) {\n    return null;\n  }\n\n  const shownQueries = staged ? queries.slice(0, progress.queries) : queries;\n  const showSources = staged ? progress.sources : sources.length > 0;\n\n  return (\n    <Collapsible\n      aria-live={staged ? \"polite\" : undefined}\n      className={cn(\"flex w-full min-w-0 flex-col\", className)}\n      data-slot=\"opencode-sources\"\n      defaultOpen={defaultOpen}\n      {...props}\n    >\n      {shownQueries.map((query) => (\n        <OpencodeActivity\n          className={cn(staged && REVEAL_CLASS)}\n          detail={`\"${query}\"`}\n          key={query}\n          kind=\"search\"\n        />\n      ))}\n      {showSources && (\n        <div className={cn(\"ps-[5ch]\", staged && REVEAL_CLASS)}>\n          <CollapsibleTrigger\n            render={\n              <Button\n                className=\"group/sources text-opencode-muted hover:text-opencode-fg aria-expanded:text-opencode-muted focus-visible:ring-opencode-blue h-auto gap-[1ch] rounded-none p-0 text-[length:inherit] leading-[inherit] font-normal hover:bg-transparent focus-visible:border-transparent focus-visible:ring-1 aria-expanded:bg-transparent dark:hover:bg-transparent\"\n                variant=\"ghost\"\n              />\n            }\n          >\n            <span\n              aria-hidden=\"true\"\n              className=\"inline-block w-[1ch] text-[0.75em] transition-transform duration-150 group-aria-expanded/sources:rotate-90 motion-reduce:transition-none\"\n            >\n              ▶\n            </span>\n            {labels.citedSources(sources.length)}\n          </CollapsibleTrigger>\n          <CollapsibleContent>\n            <ul className=\"ps-[2ch]\">\n              {sources.map((source, index) => (\n                <SourceLine\n                  delayMs={\n                    staged\n                      ? (index + 1) * OPENCODE_SEARCH_STAGGER_MS\n                      : undefined\n                  }\n                  key={source.url ?? `${source.domain}-${source.title}`}\n                  openLabel={labels.openSource}\n                  source={source}\n                />\n              ))}\n            </ul>\n          </CollapsibleContent>\n        </div>\n      )}\n    </Collapsible>\n  );\n};\n",
      "type": "registry:component",
      "target": "components/opencode/components/opencode-sources.tsx"
    },
    {
      "path": "registry/notra/opencode/components/opencode-activity.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\";\n\nimport {\n  OPENCODE_ACTIVITY_GLYPH,\n  OPENCODE_ACTIVITY_LABEL,\n  OPENCODE_THINKING_FRAME_MS,\n  OPENCODE_THINKING_FRAMES,\n} from \"../constants/opencode\";\nimport { useOpencodeFrame } from \"../hooks/use-opencode-frame\";\nimport { useOpencodeReducedMotion } from \"../hooks/use-opencode-reduced-motion\";\nimport type { OpencodeActivityProps } from \"../types/opencode\";\n\nconst TRIGGER_CLASS =\n  \"focus-visible:ring-opencode-blue h-auto justify-start gap-0 rounded-none p-0 text-start text-[length:inherit] leading-[inherit] font-normal whitespace-normal text-current hover:bg-transparent hover:text-current focus-visible:border-transparent focus-visible:ring-1 aria-expanded:bg-transparent aria-expanded:text-current dark:hover:bg-transparent\";\n\nconst ThinkingGlyph = ({ reducedMotion }: { reducedMotion?: boolean }) => {\n  const reduced = useOpencodeReducedMotion(reducedMotion);\n  const frame = useOpencodeFrame(\n    OPENCODE_THINKING_FRAMES.length,\n    OPENCODE_THINKING_FRAME_MS,\n    !reduced\n  );\n  return (\n    <span aria-hidden=\"true\" className=\"inline-block w-[2ch] shrink-0\">\n      {OPENCODE_THINKING_FRAMES[frame]}\n    </span>\n  );\n};\n\nexport const OpencodeActivity = ({\n  children,\n  className,\n  defaultOpen = false,\n  detail,\n  duration,\n  kind = \"tool\",\n  label,\n  pending = false,\n  reducedMotion,\n  ...props\n}: OpencodeActivityProps) => {\n  const isThought = kind === \"thought\";\n  const glyph = OPENCODE_ACTIVITY_GLYPH[kind];\n  const name = label ?? OPENCODE_ACTIVITY_LABEL[kind];\n\n  const summary =\n    isThought && pending ? (\n      <>\n        <ThinkingGlyph reducedMotion={reducedMotion} />\n        <span>Thinking</span>\n      </>\n    ) : (\n      <>\n        {glyph && (\n          <span aria-hidden=\"true\" className=\"inline-block w-[2ch] shrink-0\">\n            {glyph}\n          </span>\n        )}\n        <span className=\"min-w-0 wrap-break-word\">\n          {name}\n          {detail && <> {detail}</>}\n          {duration && <> · {duration}</>}\n        </span>\n      </>\n    );\n\n  return (\n    <Collapsible\n      className={cn(\n        \"ps-[3ch]\",\n        isThought ? \"text-opencode-orange\" : \"text-opencode-muted\",\n        className\n      )}\n      data-kind={kind}\n      data-slot=\"opencode-activity\"\n      defaultOpen={defaultOpen}\n      {...props}\n    >\n      {children ? (\n        <CollapsibleTrigger\n          render={<Button className={TRIGGER_CLASS} variant=\"ghost\" />}\n        >\n          <span className=\"flex min-w-0\">{summary}</span>\n        </CollapsibleTrigger>\n      ) : (\n        <div className=\"flex min-w-0\">{summary}</div>\n      )}\n      {children && (\n        <CollapsibleContent>\n          <div\n            className={cn(\n              \"text-opencode-muted mt-[1lh] whitespace-pre-wrap\",\n              isThought ? \"italic\" : \"ps-[2ch]\"\n            )}\n          >\n            {children}\n          </div>\n        </CollapsibleContent>\n      )}\n    </Collapsible>\n  );\n};\n",
      "type": "registry:component",
      "target": "components/opencode/components/opencode-activity.tsx"
    },
    {
      "path": "registry/notra/opencode/constants/opencode.ts",
      "content": "import type {\n  OpencodeActivityKind,\n  OpencodeMcpServer,\n  OpencodeMcpStatus,\n} from \"../types/opencode\";\n\nexport const OPENCODE_DEFAULT_AGENT = \"Build\";\n\nexport const OPENCODE_DEFAULT_MODEL = \"Writer 0.1\";\n\nexport const OPENCODE_DEFAULT_PROVIDER = \"Notra\";\n\nexport const OPENCODE_DEFAULT_EFFORT = \"high\";\n\nexport const OPENCODE_DEFAULT_PLACEHOLDER =\n  'Ask anything... \"Draft this week\\'s changelog\"';\n\nexport const OPENCODE_DEFAULT_VERSION = \"1.18.33\";\n\nexport const OPENCODE_DEFAULT_CWD = \"~/acme/web\";\n\nexport const OPENCODE_DEFAULT_SERVERS: OpencodeMcpServer[] = [\n  { name: \"notra\", status: \"Connected\" },\n  { name: \"github\", status: \"Connected\" },\n  { name: \"linear\", status: \"Connected\" },\n];\n\nexport const OPENCODE_MCP_STATUS_CLASS: Record<OpencodeMcpStatus, string> = {\n  Connected: \"text-opencode-green\",\n  Disconnected: \"text-opencode-muted\",\n  Failed: \"text-opencode-orange\",\n};\n\nexport const OPENCODE_ACTIVITY_GLYPH: Record<OpencodeActivityKind, string> = {\n  read: \"→\",\n  search: \"◈\",\n  thought: \"\",\n  tool: \"⚙\",\n};\n\nexport const OPENCODE_ACTIVITY_LABEL: Record<OpencodeActivityKind, string> = {\n  read: \"Read\",\n  search: \"Web Search\",\n  thought: \"Thought\",\n  tool: \"Tool\",\n};\n\nexport const OPENCODE_THINKING_FRAMES = [\n  \"⠋\",\n  \"⠙\",\n  \"⠹\",\n  \"⠸\",\n  \"⠼\",\n  \"⠴\",\n  \"⠦\",\n  \"⠧\",\n  \"⠇\",\n  \"⠏\",\n] as const;\n\nexport const OPENCODE_THINKING_FRAME_MS = 80;\n\nexport const OPENCODE_PROGRESS_CELLS = 8;\n\nexport const OPENCODE_PROGRESS_FRAME_MS = 90;\n\nexport const OPENCODE_PROGRESS_TRAIL = [1, 0.55, 0.32, 0.18] as const;\n\nexport const OPENCODE_SEARCH_HEADER_MS = 140;\n\nexport const OPENCODE_SEARCH_QUERY_MS = 280;\n\nexport const OPENCODE_SEARCH_STAGGER_MS = 56;\n\nexport const OPENCODE_SEARCH_SOURCES_MS = 160;\n\nexport const OPENCODE_CHAT_TIMING = {\n  thinkMs: 1300,\n  toolMs: 600,\n  blockMs: 260,\n  reducedMaxMs: 60,\n} as const;\n\nexport const OPENCODE_REDUCED_MOTION_QUERY = \"(prefers-reduced-motion: reduce)\";\n\nexport const OPENCODE_LOGO_ROWS = [\n  \".................................c.....\",\n  \"oooo.oooo.oooo.ooo..cccc.cccc.cccc.cccc\",\n  \"o++o.o++o.o++o.o..o.c....c++c.c++c.c++c\",\n  \"o++o.o++o.oooo.o..o.c....c++c.c++c.cccc\",\n  \"o++o.oooo.o....o..o.c....c++c.c++c.c...\",\n  \"oooo.o....oooo.o..o.cccc.cccc.cccc.cccc\",\n  \".....o.................................\",\n] as const;\n\nexport const OPENCODE_LOGO_CELL = 6;\n",
      "type": "registry:lib",
      "target": "components/opencode/constants/opencode.ts"
    },
    {
      "path": "registry/notra/opencode/types/opencode.ts",
      "content": "import type { ScrollArea as ScrollAreaPrimitive } from \"@base-ui/react/scroll-area\";\nimport type { ComponentProps, ReactNode } from \"react\";\n\nimport type { Collapsible } from \"@/components/ui/collapsible\";\n\nexport type OpencodeActivityKind = \"thought\" | \"read\" | \"search\" | \"tool\";\n\nexport type OpencodeMessageAuthor = \"user\" | \"assistant\";\n\nexport type OpencodeMcpStatus = \"Connected\" | \"Disconnected\" | \"Failed\";\n\nexport type OpencodeWindowProps = ComponentProps<\"div\">;\n\nexport type OpencodeScrollAreaProps = ScrollAreaPrimitive.Root.Props;\n\nexport interface OpencodeLogoProps extends Omit<\n  ComponentProps<\"svg\">,\n  \"height\" | \"width\"\n> {\n  /** Multiplies the 234 by 42 base size. */\n  scale?: number;\n}\n\nexport interface OpencodeMessageProps extends ComponentProps<\"div\"> {\n  /** Rendered under an assistant reply. */\n  actions?: ReactNode;\n  from?: OpencodeMessageAuthor;\n  /** Rendered above an assistant reply, usually `OpencodeSources`. */\n  search?: ReactNode;\n}\n\nexport interface OpencodeTurnFooterProps extends ComponentProps<\"div\"> {\n  agent?: string;\n  /** Marks a turn that was stopped with Escape. */\n  interrupted?: boolean;\n  /** Wall time of the turn. Omit it while the turn is still running. */\n  duration?: string;\n  model?: string;\n}\n\nexport interface OpencodeActivityProps extends ComponentProps<\n  typeof Collapsible\n> {\n  /** Arguments after the label, like a path or a query. */\n  detail?: string;\n  /** Shown after a dot, like `1.4s`. */\n  duration?: string;\n  kind?: OpencodeActivityKind;\n  /** Defaults to `Thought`, `Read` or `Web Search` by kind. */\n  label?: string;\n  /** Renders a running thought as an animated `Thinking` line. */\n  pending?: boolean;\n  /** Freezes the thinking spinner. Defaults to the user's preference. */\n  reducedMotion?: boolean;\n}\n\nexport interface OpencodeProgressProps extends ComponentProps<\"span\"> {\n  /** Animates the scanner. When false every cell rests as a dot. */\n  active?: boolean;\n  /** Freezes the scanner. Defaults to the user's preference. */\n  reducedMotion?: boolean;\n}\n\nexport interface OpencodeComposerProps extends Omit<\n  ComponentProps<\"input\">,\n  \"children\" | \"className\" | \"type\"\n> {\n  agent?: string;\n  /**\n   * Shows the scanner and `esc interrupt` instead of the cwd. Enter does not\n   * send while busy and Escape calls `onStop`.\n   */\n  busy?: boolean;\n  className?: string;\n  /** Context usage on the status row, like `63.4K (6%)`. */\n  context?: string;\n  /** Working directory shown on the status row while idle. */\n  cwd?: string;\n  effort?: string;\n  inputClassName?: string;\n  model?: string;\n  /**\n   * Called with the trimmed text on Enter. An uncontrolled composer clears\n   * afterwards; with `value`, clear it yourself here.\n   */\n  onSend?: (text: string) => void;\n  /** Called on Escape while `busy`, to interrupt the running turn. */\n  onStop?: () => void;\n  provider?: string;\n}\n\nexport interface OpencodeMcpServer {\n  name: string;\n  status?: OpencodeMcpStatus;\n}\n\nexport interface OpencodeSidebarProps extends Omit<\n  ComponentProps<\"aside\">,\n  \"title\"\n> {\n  /** Git branch appended to the cwd. Its last segment is highlighted. */\n  branch?: string;\n  cwd?: string;\n  lsp?: ReactNode;\n  mcpOpen?: ComponentProps<typeof Collapsible>[\"open\"];\n  onMcpOpenChange?: ComponentProps<typeof Collapsible>[\"onOpenChange\"];\n  servers?: OpencodeMcpServer[];\n  spent?: string;\n  title?: ReactNode;\n  tokens?: string;\n  used?: string;\n  version?: string;\n}\n\nexport interface OpencodeSource {\n  domain: string;\n  title: string;\n  url?: string;\n}\n\nexport interface OpencodeSourcesLabels {\n  citedSources: (count: number) => string;\n  openSource: (title: string, domain: string) => string;\n}\n\nexport interface OpencodeSourcesProps extends Omit<\n  ComponentProps<typeof Collapsible>,\n  \"children\"\n> {\n  labels?: OpencodeSourcesLabels;\n  queries?: readonly string[];\n  reducedMotion?: boolean;\n  sequential?: boolean;\n  sources: readonly OpencodeSource[];\n}\n\nexport interface OpencodeDemoActivity {\n  body?: string;\n  detail?: string;\n  duration?: string;\n  id: string;\n  kind: OpencodeActivityKind;\n  label?: string;\n}\n\n/** A run of reply text: plain, `code` (green) or strong (orange). */\nexport type OpencodeDemoSpan = string | { code: string } | { strong: string };\n\nexport interface OpencodeDemoListItem {\n  id: string;\n  spans: OpencodeDemoSpan[];\n}\n\nexport type OpencodeDemoBlock =\n  | { heading: string; id: string }\n  | { id: string; items: OpencodeDemoListItem[] }\n  | { id: string; text: OpencodeDemoSpan[] };\n\nexport interface OpencodeDemoTurn {\n  activities: OpencodeDemoActivity[];\n  duration?: string;\n  id: string;\n  prompt: string;\n  reply: OpencodeDemoBlock[];\n}\n\nexport interface OpencodeDemoSession {\n  agent: string;\n  branch: string;\n  context: string;\n  cwd: string;\n  effort: string;\n  model: string;\n  provider: string;\n  servers: OpencodeMcpServer[];\n  spent: string;\n  title: string;\n  tokens: string;\n  used: string;\n  version: string;\n}\n\nexport type OpencodeTurnStatus =\n  | \"working\"\n  | \"streaming\"\n  | \"done\"\n  | \"interrupted\";\n\n/** A scripted answer the demo plays back when you send a prompt. */\nexport interface OpencodeReply {\n  /** Thoughts and tool calls, played in order before the reply. */\n  activities: OpencodeDemoActivity[];\n  reply: OpencodeDemoBlock[];\n}\n\nexport interface OpencodeChatActivity extends OpencodeDemoActivity {\n  /** A thought still thinking, shown with the spinner. */\n  pending?: boolean;\n  /** Still playing back. Dropped from the transcript when interrupted. */\n  running?: boolean;\n}\n\nexport interface OpencodeChatTurn {\n  activities: OpencodeChatActivity[];\n  duration?: string;\n  id: string;\n  prompt: string;\n  reply: OpencodeDemoBlock[];\n  status: OpencodeTurnStatus;\n}\n\nexport interface OpencodeChat {\n  busy: boolean;\n  send: (text: string) => Promise<void>;\n  stop: () => void;\n  turns: OpencodeChatTurn[];\n}\n",
      "type": "registry:lib",
      "target": "components/opencode/types/opencode.ts"
    },
    {
      "path": "registry/notra/opencode/hooks/use-opencode-sources-sequence.ts",
      "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nimport {\n  OPENCODE_SEARCH_HEADER_MS,\n  OPENCODE_SEARCH_QUERY_MS,\n  OPENCODE_SEARCH_SOURCES_MS,\n} from \"../constants/opencode\";\n\ninterface OpencodeSourcesSequence {\n  queries: number;\n  sources: boolean;\n}\n\nconst HIDDEN: OpencodeSourcesSequence = { queries: 0, sources: false };\n\nexport const useOpencodeSourcesSequence = (\n  enabled: boolean,\n  queryCount: number,\n  sourceCount: number\n): OpencodeSourcesSequence => {\n  const [sequence, setSequence] = useState(HIDDEN);\n\n  useEffect(() => {\n    if (!enabled) {\n      return;\n    }\n    const timers: number[] = [];\n    const schedule = (at: number, next: Partial<OpencodeSourcesSequence>) => {\n      timers.push(\n        window.setTimeout(() => {\n          setSequence((current) => ({ ...current, ...next }));\n        }, at)\n      );\n    };\n\n    let at = OPENCODE_SEARCH_HEADER_MS;\n    for (let shown = 1; shown <= queryCount; shown += 1) {\n      schedule(at, { queries: shown });\n      at += OPENCODE_SEARCH_QUERY_MS;\n    }\n    if (sourceCount > 0) {\n      schedule(at + OPENCODE_SEARCH_SOURCES_MS, { sources: true });\n    }\n\n    return () => {\n      for (const timer of timers) {\n        window.clearTimeout(timer);\n      }\n      setSequence(HIDDEN);\n    };\n  }, [enabled, queryCount, sourceCount]);\n\n  return sequence;\n};\n",
      "type": "registry:hook",
      "target": "components/opencode/hooks/use-opencode-sources-sequence.ts"
    },
    {
      "path": "registry/notra/opencode/hooks/use-opencode-reduced-motion.ts",
      "content": "\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\n\nimport { OPENCODE_REDUCED_MOTION_QUERY } from \"../constants/opencode\";\n\nconst subscribe = (onChange: () => void) => {\n  const media = window.matchMedia(OPENCODE_REDUCED_MOTION_QUERY);\n  media.addEventListener(\"change\", onChange);\n  return () => media.removeEventListener(\"change\", onChange);\n};\n\nconst readPreference = () =>\n  window.matchMedia(OPENCODE_REDUCED_MOTION_QUERY).matches;\n\nconst readServerPreference = () => false;\n\nexport const useOpencodeReducedMotion = (override?: boolean): boolean => {\n  const prefersReduced = useSyncExternalStore(\n    subscribe,\n    readPreference,\n    readServerPreference\n  );\n  return override ?? prefersReduced;\n};\n",
      "type": "registry:hook",
      "target": "components/opencode/hooks/use-opencode-reduced-motion.ts"
    },
    {
      "path": "registry/notra/opencode/hooks/use-opencode-frame.ts",
      "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nexport const useOpencodeFrame = (\n  frameCount: number,\n  intervalMs: number,\n  active: boolean\n): number => {\n  const [frame, setFrame] = useState(0);\n\n  useEffect(() => {\n    if (!active || frameCount < 2) {\n      return;\n    }\n    const timer = window.setInterval(() => {\n      setFrame((current) => (current + 1) % frameCount);\n    }, intervalMs);\n    return () => {\n      window.clearInterval(timer);\n      setFrame(0);\n    };\n  }, [active, frameCount, intervalMs]);\n\n  return active ? frame : 0;\n};\n",
      "type": "registry:hook",
      "target": "components/opencode/hooks/use-opencode-frame.ts"
    }
  ],
  "cssVars": {
    "theme": {
      "color-opencode-bg": "var(--opencode-bg)",
      "color-opencode-panel": "var(--opencode-panel)",
      "color-opencode-sidebar": "var(--opencode-sidebar)",
      "color-opencode-fg": "var(--opencode-fg)",
      "color-opencode-muted": "var(--opencode-muted)",
      "color-opencode-subtle": "var(--opencode-subtle)",
      "color-opencode-blue": "var(--opencode-blue)",
      "color-opencode-orange": "var(--opencode-orange)",
      "color-opencode-green": "var(--opencode-green)",
      "color-opencode-logo-open": "var(--opencode-logo-open)",
      "color-opencode-logo-code": "var(--opencode-logo-code)",
      "color-opencode-logo-fill": "var(--opencode-logo-fill)",
      "font-opencode": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace"
    },
    "light": {
      "opencode-bg": "#ffffff",
      "opencode-panel": "#f5f5f5",
      "opencode-sidebar": "#fafafa",
      "opencode-fg": "#1a1a1a",
      "opencode-muted": "#6f6f6f",
      "opencode-subtle": "#e1e1e1",
      "opencode-blue": "#3b7dd8",
      "opencode-orange": "#c47a14",
      "opencode-green": "#2f8a4a",
      "opencode-logo-open": "#8a8a8a",
      "opencode-logo-code": "#1a1a1a",
      "opencode-logo-fill": "#dcdcdc"
    },
    "dark": {
      "opencode-bg": "#090909",
      "opencode-panel": "#191919",
      "opencode-sidebar": "#121212",
      "opencode-fg": "#e9e9e9",
      "opencode-muted": "#737373",
      "opencode-subtle": "#262626",
      "opencode-blue": "#3b91f9",
      "opencode-orange": "#fd9718",
      "opencode-green": "#4fd47b",
      "opencode-logo-open": "#737373",
      "opencode-logo-code": "#e9e9e9",
      "opencode-logo-fill": "#2a2a2a"
    }
  },
  "type": "registry:component"
}