{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "template-docs",
  "title": "Tern: documentation",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A five-page documentation site: search-first landing, a guide with synced language tabs, filterable API reference, search with highlighted matches and a changelog, with ⌘K search in the header.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/command.json",
    "https://ui.ballmac.com/r/kbd.json",
    "https://ui.ballmac.com/r/sheet.json",
    "https://ui.ballmac.com/r/snippet-tabs.json",
    "https://ui.ballmac.com/r/install-tabs.json",
    "https://ui.ballmac.com/r/code-block.json",
    "https://ui.ballmac.com/r/callout.json",
    "https://ui.ballmac.com/r/copy-button.json",
    "https://ui.ballmac.com/r/table-of-contents.json",
    "https://ui.ballmac.com/r/api-endpoint.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/templates/docs/docs-fonts.ts",
      "content": "// Ballmac UI: Docs template fonts. https://ui.ballmac.com/templates/template-docs\nimport { Instrument_Sans, JetBrains_Mono, Newsreader } from \"next/font/google\"\n\nexport const docsSerif = Newsreader({ variable: \"--docs-serif\", subsets: [\"latin\"], display: \"swap\", preload: false })\nexport const docsSans = Instrument_Sans({ variable: \"--docs-sans\", subsets: [\"latin\"], display: \"swap\", preload: false })\nexport const docsMono = JetBrains_Mono({ variable: \"--docs-mono\", subsets: [\"latin\"], display: \"swap\", preload: false })\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-fonts.ts"
    },
    {
      "path": "registry/ballmac/components/templates/docs/docs-data.ts",
      "content": "// Ballmac UI: Docs template data. https://ui.ballmac.com/templates/template-docs\nexport type DocsKind = \"Guide\" | \"API\" | \"Changelog\"\n\nexport type DocsEntry = { title: string; kind: DocsKind; path: string; summary: string; page: \"guide\" | \"reference\" | \"changelog\" }\n\n/** Everything the search page and the command menu can find. */\nexport const searchIndex: DocsEntry[] = [\n  { title: \"Send your first message\", kind: \"Guide\", path: \"Get started\", summary: \"Install the SDK, create a queue, send a message and acknowledge it in about seven minutes.\", page: \"guide\" },\n  { title: \"Delivery guarantees\", kind: \"Guide\", path: \"Concepts\", summary: \"At-least-once delivery, visibility timeouts and how a message is retried before it reaches the dead-letter queue.\", page: \"guide\" },\n  { title: \"Dead-letter queues\", kind: \"Guide\", path: \"Concepts\", summary: \"Park messages that keep failing, inspect them and replay them back into the queue once the bug is fixed.\", page: \"guide\" },\n  { title: \"Idempotent consumers\", kind: \"Guide\", path: \"Guides\", summary: \"Use the message id as an idempotency key so a retried message never charges a card twice.\", page: \"guide\" },\n  { title: \"Scheduling and delays\", kind: \"Guide\", path: \"Guides\", summary: \"Deliver a message in thirty seconds or next Tuesday with delay and a cron-style schedule.\", page: \"guide\" },\n  { title: \"Verify webhook signatures\", kind: \"Guide\", path: \"Guides\", summary: \"Check the Tern-Signature header with an HMAC before you trust a push delivery.\", page: \"guide\" },\n  { title: \"Create a queue\", kind: \"API\", path: \"POST /v1/queues\", summary: \"Creates a queue with a name, a visibility timeout and an optional dead-letter target.\", page: \"reference\" },\n  { title: \"List queues\", kind: \"API\", path: \"GET /v1/queues\", summary: \"Returns every queue in the project, newest first, with a cursor for the next page.\", page: \"reference\" },\n  { title: \"Send a message\", kind: \"API\", path: \"POST /v1/queues/{queue}/messages\", summary: \"Adds a message to a queue. Pass an idempotency key to make retries safe.\", page: \"reference\" },\n  { title: \"Receive messages\", kind: \"API\", path: \"GET /v1/queues/{queue}/messages\", summary: \"Long-polls a queue and hides each message from other consumers until its visibility timeout ends.\", page: \"reference\" },\n  { title: \"Acknowledge a message\", kind: \"API\", path: \"DELETE /v1/queues/{queue}/messages/{id}\", summary: \"Deletes a message once it has been processed successfully.\", page: \"reference\" },\n  { title: \"Purge a queue\", kind: \"API\", path: \"POST /v1/queues/{queue}/purge\", summary: \"Removes every waiting message. This cannot be undone.\", page: \"reference\" },\n  { title: \"3.2: Message replay\", kind: \"Changelog\", path: \"September 2026\", summary: \"Replay any window of the last 14 days into a queue without touching producers.\", page: \"changelog\" },\n  { title: \"3.1: Batch receive\", kind: \"Changelog\", path: \"August 2026\", summary: \"Receive up to 50 messages per call and acknowledge them in one request.\", page: \"changelog\" },\n  { title: \"3.0: Regions\", kind: \"Changelog\", path: \"June 2026\", summary: \"Queues can now live in Frankfurt, Virginia or Singapore. Breaking: the default region is explicit.\", page: \"changelog\" },\n]\n\nexport type NavSection = { title: string; items: { title: string; page: \"guide\" | \"reference\" | \"changelog\"; current?: boolean }[] }\n\nexport const nav: NavSection[] = [\n  { title: \"Get started\", items: [{ title: \"Send your first message\", page: \"guide\", current: true }, { title: \"Core concepts\", page: \"guide\" }, { title: \"Authentication\", page: \"reference\" }] },\n  { title: \"Concepts\", items: [{ title: \"Delivery guarantees\", page: \"guide\" }, { title: \"Visibility timeouts\", page: \"guide\" }, { title: \"Dead-letter queues\", page: \"guide\" }] },\n  { title: \"Guides\", items: [{ title: \"Idempotent consumers\", page: \"guide\" }, { title: \"Scheduling and delays\", page: \"guide\" }, { title: \"Verify webhook signatures\", page: \"guide\" }, { title: \"Going to production\", page: \"guide\" }] },\n  { title: \"API reference\", items: [{ title: \"Queues\", page: \"reference\", current: true }, { title: \"Messages\", page: \"reference\" }, { title: \"Errors and limits\", page: \"reference\" }] },\n  { title: \"Releases\", items: [{ title: \"Changelog\", page: \"changelog\", current: true }] },\n]\n\nexport type Release = { version: string; date: string; title: string; tag: \"Added\" | \"Improved\" | \"Fixed\" | \"Breaking\"; body: string; points: string[] }\n\nexport const releases: Release[] = [\n  { version: \"3.2.0\", date: \"2026-09-24\", title: \"Message replay\", tag: \"Added\", body: \"Replay a window of past messages into any queue. Useful after a bad deploy, or to seed a new environment with real traffic.\", points: [\"Replay up to 14 days of history by time range or by message id\", \"Replays are rate limited per queue so they never starve live traffic\", \"New `tern replay` command and `POST /v1/queues/{queue}/replays`\"] },\n  { version: \"3.1.4\", date: \"2026-09-10\", title: \"Faster long polling\", tag: \"Improved\", body: \"Receive calls now wake within 40 ms of a message arriving instead of waiting for the next poll tick.\", points: [\"Median receive latency fell from 180 ms to 46 ms\", \"No change needed: the improvement applies to every SDK\"] },\n  { version: \"3.1.3\", date: \"2026-08-29\", title: \"Visibility timeout on batch receive\", tag: \"Fixed\", body: \"Messages received in a batch of 50 could become visible again early if the first acknowledgement was slow.\", points: [\"The visibility timer now starts per message, not per batch\", \"Affected SDKs: node 3.1.0 to 3.1.2, python 3.1.0 to 3.1.1\"] },\n  { version: \"3.1.0\", date: \"2026-08-12\", title: \"Batch receive and acknowledge\", tag: \"Added\", body: \"Receive up to 50 messages per call and acknowledge them in a single request. Throughput on busy queues roughly doubles.\", points: [\"`maxMessages` on receive, `ids` on acknowledge\", \"Partial failures return a per-message result\", \"SDK helpers `queue.consume()` batch for you\"] },\n  { version: \"3.0.0\", date: \"2026-06-03\", title: \"Regions are explicit\", tag: \"Breaking\", body: \"Queues now live in a region you choose. The old global endpoint is retired, so clients must set a region.\", points: [\"Set `region` when creating the client: `eu`, `us` or `ap`\", \"Existing queues were migrated to `us` with no downtime\", \"The global endpoint returns 410 on 1 December 2026\"] },\n  { version: \"2.9.2\", date: \"2026-05-14\", title: \"Clearer error messages\", tag: \"Improved\", body: \"Every error now includes a stable `code`, a human sentence and a link to the page that explains the fix.\", points: [\"`queue_not_found`, `visibility_expired` and 14 more codes documented\", \"Errors in the dashboard link straight to the docs\"] },\n]\n\nexport const languages = [\"Node\", \"Python\", \"Go\", \"cURL\"] as const\n\nexport const endpointGroups = [\n  {\n    title: \"Queues\",\n    endpoints: [\n      {\n        method: \"POST\" as const, path: \"/v1/queues\", summary: \"Create a queue\", description: \"Creates a queue in the region of the client. Names are unique per project.\",\n        parameters: [\n          { name: \"name\", in: \"body\" as const, type: \"string\", required: true, description: \"Lowercase letters, numbers and dashes, up to 64 characters.\" },\n          { name: \"visibilityTimeout\", in: \"body\" as const, type: \"integer\", description: \"Seconds a received message stays hidden.\", default: \"30\" },\n          { name: \"deadLetter\", in: \"body\" as const, type: \"string\", description: \"Name of the queue that receives messages after maxReceives failures.\" },\n          { name: \"maxReceives\", in: \"body\" as const, type: \"integer\", description: \"Receives before a message is moved to the dead-letter queue.\", default: \"5\" },\n        ],\n        requestExample: `{\\n  \"name\": \"invoices\",\\n  \"visibilityTimeout\": 60,\\n  \"deadLetter\": \"invoices-dead\",\\n  \"maxReceives\": 5\\n}`,\n        responses: [\n          { status: 201, description: \"Queue created\", example: `{\\n  \"name\": \"invoices\",\\n  \"region\": \"eu\",\\n  \"visibilityTimeout\": 60,\\n  \"createdAt\": \"2026-09-30T08:14:22Z\"\\n}` },\n          { status: 409, description: \"A queue with this name already exists\", example: `{\\n  \"code\": \"queue_exists\",\\n  \"message\": \"A queue named invoices already exists in eu.\"\\n}` },\n        ],\n      },\n      {\n        method: \"GET\" as const, path: \"/v1/queues\", summary: \"List queues\", description: \"Returns the queues in the project, newest first.\",\n        parameters: [\n          { name: \"limit\", in: \"query\" as const, type: \"integer\", description: \"Queues per page, up to 100.\", default: \"25\" },\n          { name: \"cursor\", in: \"query\" as const, type: \"string\", description: \"The `next` value from the previous page.\" },\n        ],\n        responses: [{ status: 200, description: \"A page of queues\", example: `{\\n  \"data\": [{ \"name\": \"invoices\", \"waiting\": 12, \"inFlight\": 3 }],\\n  \"next\": null\\n}` }],\n      },\n      {\n        method: \"POST\" as const, path: \"/v1/queues/{queue}/purge\", summary: \"Purge a queue\", description: \"Removes every waiting message. In-flight messages finish normally. This cannot be undone.\",\n        parameters: [{ name: \"queue\", in: \"path\" as const, type: \"string\", required: true, description: \"The queue name.\" }],\n        responses: [{ status: 202, description: \"Purge accepted\", example: `{ \"removed\": 1204 }` }, { status: 404, description: \"No such queue\", example: `{ \"code\": \"queue_not_found\" }` }],\n      },\n    ],\n  },\n  {\n    title: \"Messages\",\n    endpoints: [\n      {\n        method: \"POST\" as const, path: \"/v1/queues/{queue}/messages\", summary: \"Send a message\", description: \"Adds a message to the queue. Send an `Idempotency-Key` header and a retry will return the first result instead of adding a duplicate.\",\n        parameters: [\n          { name: \"queue\", in: \"path\" as const, type: \"string\", required: true, description: \"The queue name.\" },\n          { name: \"Idempotency-Key\", in: \"header\" as const, type: \"string\", description: \"Any string up to 255 characters. Keys expire after 24 hours.\" },\n          { name: \"body\", in: \"body\" as const, type: \"object\", required: true, description: \"Any JSON up to 256 KB.\" },\n          { name: \"delay\", in: \"body\" as const, type: \"integer\", description: \"Seconds before the message becomes visible, up to 15 minutes.\", default: \"0\" },\n        ],\n        requestExample: `{\\n  \"body\": { \"invoice\": \"inv_2041\", \"total\": 4200 },\\n  \"delay\": 0\\n}`,\n        responses: [{ status: 201, description: \"Message accepted\", example: `{\\n  \"id\": \"msg_7Hq2Zc\",\\n  \"queue\": \"invoices\",\\n  \"visibleAt\": \"2026-09-30T08:14:22Z\"\\n}` }, { status: 413, description: \"Body is larger than 256 KB\", example: `{ \"code\": \"body_too_large\" }` }],\n      },\n      {\n        method: \"GET\" as const, path: \"/v1/queues/{queue}/messages\", summary: \"Receive messages\", description: \"Long-polls for up to `wait` seconds. Each message is hidden from other consumers until its visibility timeout ends.\",\n        parameters: [\n          { name: \"queue\", in: \"path\" as const, type: \"string\", required: true, description: \"The queue name.\" },\n          { name: \"maxMessages\", in: \"query\" as const, type: \"integer\", description: \"Up to 50 per call.\", default: \"1\" },\n          { name: \"wait\", in: \"query\" as const, type: \"integer\", description: \"Seconds to wait for a message, up to 20.\", default: \"0\" },\n        ],\n        responses: [{ status: 200, description: \"Zero or more messages\", example: `{\\n  \"data\": [\\n    { \"id\": \"msg_7Hq2Zc\", \"receipt\": \"rcpt_91\", \"body\": { \"invoice\": \"inv_2041\" }, \"receives\": 1 }\\n  ]\\n}` }],\n      },\n      {\n        method: \"DELETE\" as const, path: \"/v1/queues/{queue}/messages/{id}\", summary: \"Acknowledge a message\", description: \"Deletes a message once it has been processed. Use the receipt from the receive call.\",\n        parameters: [\n          { name: \"queue\", in: \"path\" as const, type: \"string\", required: true, description: \"The queue name.\" },\n          { name: \"id\", in: \"path\" as const, type: \"string\", required: true, description: \"The message id.\" },\n          { name: \"receipt\", in: \"query\" as const, type: \"string\", required: true, description: \"Proves you hold the message right now.\" },\n        ],\n        responses: [{ status: 204, description: \"Deleted\" }, { status: 410, description: \"The visibility timeout ended; the message may already be with another consumer\", example: `{ \"code\": \"visibility_expired\" }` }],\n      },\n    ],\n  },\n]\n\nexport const snippets = {\n  install: { Node: \"npm install @tern/sdk\", Python: \"pip install tern\", Go: \"go get github.com/tern/tern-go\", cURL: \"# nothing to install\" },\n}\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-data.ts"
    },
    {
      "path": "registry/ballmac/components/templates/docs/docs-theme.tsx",
      "content": "// Ballmac UI: Docs template shell. https://ui.ballmac.com/templates/template-docs\n\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronRight, Menu, Search } from \"lucide-react\"\n\nimport { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from \"@/components/ballmac/command\"\nimport { Kbd } from \"@/components/ballmac/kbd\"\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from \"@/components/ballmac/sheet\"\nimport { nav, searchIndex, type DocsKind } from \"@/components/ballmac/templates/docs/docs-data\"\nimport { docsMono, docsSans, docsSerif } from \"@/components/ballmac/templates/docs/docs-fonts\"\nimport { cn } from \"@/lib/utils\"\n\ntype DocsPage = \"home\" | \"guide\" | \"reference\" | \"search\" | \"changelog\"\ntype DocsHrefs = Record<DocsPage, string>\n\nconst defaultHrefs: DocsHrefs = { home: \"/docs\", guide: \"/docs/guides\", reference: \"/docs/reference\", search: \"/docs/search\", changelog: \"/docs/changelog\" }\n\n/** Tern's palette: a cool, quiet page with deep teal ink. Dark mode is the same page lit by a terminal. */\nconst docsCss = `\n.docs-theme,body:not(:has([data-gallery])):has(.docs-theme){--background:oklch(0.985 0.006 210);--foreground:oklch(0.2 0.025 235);--card:oklch(1 0 0);--card-foreground:oklch(0.2 0.025 235);--popover:oklch(1 0 0);--popover-foreground:oklch(0.2 0.025 235);--primary:oklch(0.4 0.09 200);--primary-foreground:oklch(0.985 0.006 210);--secondary:oklch(0.955 0.012 210);--secondary-foreground:oklch(0.2 0.025 235);--muted:oklch(0.955 0.012 210);--muted-foreground:oklch(0.46 0.03 235);--accent:oklch(0.945 0.02 200);--accent-foreground:oklch(0.2 0.025 235);--border:oklch(0.2 0.025 235 / 12%);--input:oklch(0.2 0.025 235 / 18%);--ring:oklch(0.5 0.11 195);--surface:oklch(0.97 0.01 210);--destructive:oklch(0.52 0.21 27);--chart-1:oklch(0.5 0.11 190);--chart-2:oklch(0.5 0.15 265);--chart-3:oklch(0.62 0.15 75);--chart-4:oklch(0.55 0.15 150);--chart-5:oklch(0.55 0.2 20);--radius:0.75rem}\n.dark .docs-theme,.dark body:not(:has([data-gallery])):has(.docs-theme){--background:oklch(0.165 0.02 235);--foreground:oklch(0.95 0.01 205);--card:oklch(0.2 0.022 235);--card-foreground:oklch(0.95 0.01 205);--popover:oklch(0.22 0.024 235);--popover-foreground:oklch(0.95 0.01 205);--primary:oklch(0.8 0.11 190);--primary-foreground:oklch(0.18 0.03 235);--secondary:oklch(0.25 0.025 235);--secondary-foreground:oklch(0.95 0.01 205);--muted:oklch(0.25 0.025 235);--muted-foreground:oklch(0.72 0.03 215);--accent:oklch(0.28 0.03 225);--accent-foreground:oklch(0.95 0.01 205);--border:oklch(1 0 0 / 10%);--input:oklch(1 0 0 / 15%);--ring:oklch(0.8 0.11 190);--surface:oklch(0.19 0.021 235);--destructive:oklch(0.7 0.19 27);--chart-1:oklch(0.8 0.11 190);--chart-2:oklch(0.76 0.12 265);--chart-3:oklch(0.82 0.13 80);--chart-4:oklch(0.78 0.13 150);--chart-5:oklch(0.74 0.14 20)}\nbody:not(:has([data-gallery])):has(.docs-theme){font-family:var(--docs-sans),ui-sans-serif,system-ui,sans-serif}\n@keyframes docs-packet{0%{transform:translateX(0);opacity:0}10%{opacity:1}90%{opacity:1}100%{transform:translateX(var(--docs-travel,10rem));opacity:0}}\n.docs-packet{animation:docs-packet 2.6s ease-in-out infinite}\n@media (prefers-reduced-motion:reduce){.docs-packet{animation:none;opacity:1}}\n`\n\nconst serif = \"[font-family:var(--docs-serif),ui-serif,Georgia,serif] font-medium tracking-[-0.015em]\"\nconst mono = \"[font-family:var(--docs-mono),ui-monospace,monospace]\"\n\nconst kindStyle: Record<DocsKind, string> = { Guide: \"bg-chart-1/15\", API: \"bg-chart-2/15\", Changelog: \"bg-chart-3/20\" }\n\nfunction Logo({ className }: { className?: string }) {\n  return (\n    <span aria-hidden=\"true\" className={cn(\"bg-primary text-primary-foreground inline-flex size-8 items-center justify-center rounded-lg\", className)}>\n      <svg viewBox=\"0 0 24 24\" className=\"size-5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M4 8h9a4 4 0 0 1 0 8H8\" /><path d=\"m11 13-3 3 3 3\" /></svg>\n    </span>\n  )\n}\n\ntype SidebarProps = { page: DocsPage; hrefs: DocsHrefs; onNavigate?: () => void }\nfunction SidebarNav({ page, hrefs, onNavigate }: SidebarProps) {\n  return (\n    <nav aria-label=\"Documentation\" className=\"grid gap-6 text-sm\">\n      {nav.map((section) => (\n        <div key={section.title}>\n          <h2 className=\"text-foreground mb-2 px-2 text-xs font-bold tracking-[0.12em] uppercase\">{section.title}</h2>\n          <ul className=\"grid gap-0.5\">\n            {section.items.map((item) => {\n              const current = item.page === page && !!item.current\n              return (\n                <li key={item.title}>\n                  <a\n                    href={hrefs[item.page]}\n                    onClick={onNavigate}\n                    aria-current={current ? \"page\" : undefined}\n                    className=\"text-muted-foreground hover:text-foreground aria-[current=page]:bg-accent aria-[current=page]:text-foreground focus-visible:ring-ring/50 relative block rounded-md px-2 py-1.5 outline-none transition-colors focus-visible:ring-[3px] aria-[current=page]:font-semibold motion-reduce:transition-none\"\n                  >\n                    {item.title}\n                  </a>\n                </li>\n              )\n            })}\n          </ul>\n        </div>\n      ))}\n    </nav>\n  )\n}\n\ntype DocsShellProps = React.ComponentProps<\"div\"> & {\n  /** The page being shown, so its nav link is marked current. */\n  page: DocsPage\n  /** Override where pages live (used by previews). */\n  hrefs?: Partial<DocsHrefs>\n  /** Show the left navigation. Landing and search pages turn it off. */\n  sidebar?: boolean\n}\n\n/** Tern's frame: a sticky header with the ⌘K search, a version picker, a sidebar and a footer. */\nfunction DocsShell({ page, hrefs: overrides, sidebar = true, className, style, children, ...props }: DocsShellProps) {\n  const hrefs = { ...defaultHrefs, ...overrides }\n  const [searchOpen, setSearchOpen] = React.useState(false)\n  const [menuOpen, setMenuOpen] = React.useState(false)\n  React.useEffect(() => {\n    const classes = [docsSerif.variable, docsSans.variable, docsMono.variable].filter(Boolean)\n    document.body.classList.add(...classes)\n    return () => document.body.classList.remove(...classes)\n  }, [])\n  return (\n    <div\n      data-slot=\"docs\"\n      className={cn(\"docs-theme bg-background text-foreground relative min-h-dvh overflow-x-clip\", docsSerif.variable, docsSans.variable, docsMono.variable, className)}\n      style={{ fontFamily: \"var(--docs-sans), ui-sans-serif, system-ui, sans-serif\", ...style }}\n      {...props}\n    >\n      <style>{docsCss}</style>\n      <header className=\"bg-background/85 sticky top-0 z-40 border-b backdrop-blur-xl\">\n        <div className=\"mx-auto flex h-16 max-w-[90rem] items-center gap-3 px-4 sm:px-6\">\n          {sidebar && (\n            <Sheet open={menuOpen} onOpenChange={setMenuOpen}>\n              <SheetTrigger className=\"hover:bg-accent focus-visible:ring-ring/50 inline-flex size-10 items-center justify-center rounded-lg outline-none focus-visible:ring-[3px] lg:hidden\" aria-label=\"Open navigation\"><Menu className=\"size-5\" aria-hidden=\"true\" /></SheetTrigger>\n              <SheetContent side=\"start\" className=\"docs-theme bg-background w-80 overflow-y-auto p-5\">\n                <SheetHeader className=\"p-0 pb-4\"><SheetTitle>Tern docs</SheetTitle><SheetDescription>Browse every guide and the API reference.</SheetDescription></SheetHeader>\n                <SidebarNav page={page} hrefs={hrefs} onNavigate={() => setMenuOpen(false)} />\n              </SheetContent>\n            </Sheet>\n          )}\n          <a href={hrefs.home} className=\"focus-visible:ring-ring/50 flex items-center gap-2.5 rounded-lg outline-none focus-visible:ring-[3px]\">\n            <Logo />\n            <span className={cn(\"text-2xl\", serif)}>Tern</span>\n            <span className=\"text-muted-foreground hidden border-s ps-2.5 text-sm font-semibold sm:inline\">Docs</span>\n          </a>\n          <nav aria-label=\"Main\" className=\"ms-4 hidden items-center gap-1 text-sm font-medium md:flex\">\n            {([[\"guide\", \"Guides\"], [\"reference\", \"API reference\"], [\"changelog\", \"Changelog\"]] as const).map(([key, label]) => (\n              <a key={key} href={hrefs[key]} aria-current={page === key ? \"page\" : undefined} className=\"text-muted-foreground hover:text-foreground aria-[current=page]:text-foreground focus-visible:ring-ring/50 rounded-md px-3 py-2 outline-none transition-colors focus-visible:ring-[3px] aria-[current=page]:font-semibold motion-reduce:transition-none\">{label}</a>\n            ))}\n          </nav>\n          <div className=\"ms-auto flex items-center gap-2\">\n            <button type=\"button\" onClick={() => setSearchOpen(true)} className=\"bg-surface hover:bg-accent focus-visible:ring-ring/50 text-muted-foreground inline-flex h-10 items-center gap-2 rounded-lg border px-3 text-sm outline-none transition-colors focus-visible:ring-[3px] sm:w-64 motion-reduce:transition-none\" aria-label=\"Search the docs\">\n              <Search className=\"size-4 shrink-0\" aria-hidden=\"true\" />\n              <span className=\"hidden flex-1 text-start sm:inline\">Search the docs</span>\n              <span className=\"hidden items-center gap-1 sm:inline-flex\" aria-hidden=\"true\"><Kbd>⌘</Kbd><Kbd>K</Kbd></span>\n            </button>\n            <label className=\"sr-only\" htmlFor=\"docs-version\">API version</label>\n            <select id=\"docs-version\" defaultValue=\"v3\" className=\"bg-background focus-visible:ring-ring/50 h-10 rounded-lg border px-2.5 text-sm font-medium outline-none focus-visible:ring-[3px]\">\n              <option value=\"v3\">v3.2</option>\n              <option value=\"v2\">v2.9</option>\n            </select>\n          </div>\n        </div>\n      </header>\n\n      <CommandDialog open={searchOpen} onOpenChange={setSearchOpen} title=\"Search the docs\" description=\"Type to search guides, the API reference and the changelog.\">\n        <Command>\n          <CommandInput placeholder=\"Search guides, endpoints and releases\" />\n          <CommandList>\n            <CommandEmpty>No results. Try “queue” or “retry”.</CommandEmpty>\n            {([\"Guide\", \"API\", \"Changelog\"] as const).map((kind) => (\n              <CommandGroup key={kind} heading={kind === \"API\" ? \"API reference\" : kind === \"Guide\" ? \"Guides\" : \"Releases\"}>\n                {searchIndex.filter((e) => e.kind === kind).map((e) => (\n                  <CommandItem key={e.title} value={`${e.title} ${e.path} ${e.summary}`} onSelect={() => { setSearchOpen(false); window.location.href = hrefs[e.page] }}>\n                    <span className=\"grid min-w-0\"><span className=\"truncate font-medium\">{e.title}</span><span className={cn(\"text-muted-foreground truncate text-xs\", kind === \"API\" && mono)}>{e.path}</span></span>\n                  </CommandItem>\n                ))}\n              </CommandGroup>\n            ))}\n          </CommandList>\n        </Command>\n      </CommandDialog>\n\n      {sidebar ? (\n        <div className=\"mx-auto grid max-w-[90rem] grid-cols-[minmax(0,1fr)] lg:grid-cols-[16rem_minmax(0,1fr)]\">\n          <aside className=\"sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto border-e p-6 pe-4 lg:block\" aria-label=\"Sidebar\">\n            <SidebarNav page={page} hrefs={hrefs} />\n          </aside>\n          <div className=\"min-w-0\">{children}</div>\n        </div>\n      ) : (\n        children\n      )}\n\n      <footer className=\"border-t\">\n        <div className=\"mx-auto grid max-w-[90rem] gap-8 px-4 py-12 sm:px-6 md:grid-cols-[1.4fr_1fr_1fr_1fr]\">\n          <div><a href={hrefs.home} className=\"flex items-center gap-2.5\"><Logo className=\"size-7\" /><span className={cn(\"text-xl\", serif)}>Tern</span></a><p className=\"text-muted-foreground mt-3 max-w-xs text-sm text-pretty\">Queues that never lose a message. Built by people who have been paged at 3 a.m.</p></div>\n          {[\n            [\"Learn\", [[\"Quickstart\", hrefs.guide], [\"Concepts\", hrefs.guide], [\"Guides\", hrefs.guide]]],\n            [\"Build\", [[\"API reference\", hrefs.reference], [\"SDKs\", hrefs.reference], [\"Changelog\", hrefs.changelog]]],\n            [\"Help\", [[\"Search\", hrefs.search], [\"Status\", hrefs.home], [\"Contact support\", hrefs.home]]],\n          ].map(([title, items]) => (\n            <div key={title as string}><h2 className=\"text-xs font-bold tracking-[0.12em] uppercase\">{title as string}</h2><ul className=\"mt-3 grid gap-2 text-sm\">{(items as string[][]).map(([l, h]) => <li key={l}><a href={h} className=\"text-muted-foreground hover:text-foreground\">{l}</a></li>)}</ul></div>\n          ))}\n        </div>\n        <p className=\"text-muted-foreground mx-auto max-w-[90rem] border-t px-4 py-5 text-xs sm:px-6\">© 2026 Tern Systems. All systems operational.</p>\n      </footer>\n    </div>\n  )\n}\n\n/** A small crumb row: \"Get started › Send your first message\". */\nfunction Crumbs({ items }: { items: string[] }) {\n  return (\n    <nav aria-label=\"Breadcrumb\" className=\"text-muted-foreground flex flex-wrap items-center gap-1 text-sm\">\n      {items.map((it, i) => (\n        <React.Fragment key={it}>\n          {i > 0 && <ChevronRight className=\"size-3.5 rtl:rotate-180\" aria-hidden=\"true\" />}\n          <span aria-current={i === items.length - 1 ? \"page\" : undefined} className={i === items.length - 1 ? \"text-foreground font-medium\" : undefined}>{it}</span>\n        </React.Fragment>\n      ))}\n    </nav>\n  )\n}\n\nexport { Crumbs, DocsShell, Logo as DocsLogo, defaultHrefs as docsDefaultHrefs, kindStyle as docsKindStyle, mono as docsMonoClass, serif as docsSerifClass, type DocsHrefs, type DocsPage, type DocsShellProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-theme.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/docs/docs-home.tsx",
      "content": "// Ballmac UI: Docs home page. https://ui.ballmac.com/templates/template-docs\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight, BookOpen, Code2, History, Rocket, Search } from \"lucide-react\"\n\nimport { SnippetTabs } from \"@/components/ballmac/snippet-tabs\"\nimport { releases, searchIndex, snippets } from \"@/components/ballmac/templates/docs/docs-data\"\nimport { DocsShell, docsMonoClass, docsSerifClass, type DocsHrefs } from \"@/components/ballmac/templates/docs/docs-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst paths = [\n  { icon: Rocket, title: \"Quickstart\", body: \"Send and receive your first message in about seven minutes.\", key: \"guide\" as const, tint: \"bg-chart-1/15\" },\n  { icon: BookOpen, title: \"Guides\", body: \"Retries, dead letters, schedules and idempotent consumers, explained with code.\", key: \"guide\" as const, tint: \"bg-chart-2/15\" },\n  { icon: Code2, title: \"API reference\", body: \"Every endpoint, parameter and error code, with examples in four languages.\", key: \"reference\" as const, tint: \"bg-chart-3/20\" },\n  { icon: History, title: \"Changelog\", body: \"What shipped, what changed and what to do about it.\", key: \"changelog\" as const, tint: \"bg-chart-4/15\" },\n]\n\n/** Producer, queue and consumer with packets travelling between them. Decorative; the sentence below carries the meaning. */\nfunction Flow() {\n  const box = \"bg-card grid h-20 w-24 shrink-0 place-items-center rounded-xl border text-center text-xs font-semibold shadow-xs sm:w-32 sm:text-sm\"\n  return (\n    <figure className=\"mx-auto w-full max-w-xl\" aria-label=\"How a message travels\">\n      <div aria-hidden=\"true\" className=\"flex items-center\">\n        <div className={box}><span>Producer<span className={cn(\"text-muted-foreground block text-xs font-normal\", docsMonoClass)}>your app</span></span></div>\n        <div className=\"relative h-px min-w-6 flex-1 border-t-2 border-dashed [--docs-travel:1.75rem] sm:[--docs-travel:5rem]\"><span className=\"docs-packet bg-chart-1 absolute -top-[7px] start-0 size-3 rounded-full\" /></div>\n        <div className={cn(box, \"bg-primary text-primary-foreground border-primary\")}><span>Queue<span className={cn(\"block text-xs font-normal opacity-100\", docsMonoClass)}>invoices</span></span></div>\n        <div className=\"relative h-px min-w-6 flex-1 border-t-2 border-dashed [--docs-travel:1.75rem] sm:[--docs-travel:5rem]\"><span className=\"docs-packet bg-chart-3 absolute -top-[7px] start-0 size-3 rounded-full\" style={{ animationDelay: \"1.1s\" }} /></div>\n        <div className={box}><span>Consumer<span className={cn(\"text-muted-foreground block text-xs font-normal\", docsMonoClass)}>worker</span></span></div>\n      </div>\n      <figcaption className=\"text-muted-foreground mt-4 text-center text-sm\">A producer sends, Tern holds the message until a consumer acknowledges it.</figcaption>\n    </figure>\n  )\n}\n\ntype DocsHomeProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<DocsHrefs> }\n\n/** The Docs landing page: a search-first hero, four paths in, install tabs and the latest releases. */\nfunction DocsHome({ hrefs, ...props }: DocsHomeProps) {\n  const link = { ...{ home: \"/docs\", guide: \"/docs/guides\", reference: \"/docs/reference\", search: \"/docs/search\", changelog: \"/docs/changelog\" }, ...hrefs }\n  return (\n    <DocsShell page=\"home\" hrefs={hrefs} sidebar={false} {...props}>\n      <main>\n        <section aria-labelledby=\"dh-title\" className=\"relative isolate overflow-hidden border-b\">\n          <div aria-hidden=\"true\" className=\"absolute inset-0 -z-10 [background-image:radial-gradient(var(--border)_1.2px,transparent_1.2px)] [background-size:22px_22px] [mask-image:linear-gradient(to_bottom,black,transparent_85%)]\" />\n          <div className=\"mx-auto max-w-5xl px-4 pt-16 pb-14 text-center sm:px-6 sm:pt-24\">\n            <p className={cn(\"bg-accent text-accent-foreground inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-semibold\", docsMonoClass)}><span className=\"bg-chart-4 size-1.5 rounded-full\" aria-hidden=\"true\" />v3.2 · message replay is here</p>\n            <h1 id=\"dh-title\" className={cn(\"mx-auto mt-6 max-w-3xl text-[clamp(2.8rem,7vw,5.5rem)] leading-[1.02] text-balance\", docsSerifClass)}>Queues that <em className=\"text-primary\">never lose</em> a message.</h1>\n            <p className=\"text-muted-foreground mx-auto mt-5 max-w-xl text-lg text-pretty\">Everything you need to send, receive and retry messages at any scale, written by the people who run the queues.</p>\n            <form action={link.search} method=\"get\" role=\"search\" className=\"mx-auto mt-9 flex max-w-xl items-center gap-2\">\n              <label htmlFor=\"dh-q\" className=\"sr-only\">Search the docs</label>\n              <div className=\"bg-card focus-within:ring-ring/50 relative flex-1 rounded-xl border shadow-sm focus-within:ring-[3px]\">\n                <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-4 size-5 -translate-y-1/2\" aria-hidden=\"true\" />\n                <input id=\"dh-q\" name=\"q\" type=\"search\" placeholder=\"Search: retries, idempotency, dead letters…\" className=\"h-14 w-full rounded-xl bg-transparent pe-4 ps-12 text-base outline-none\" />\n              </div>\n              <button type=\"submit\" className=\"bg-primary text-primary-foreground focus-visible:ring-ring/50 h-14 rounded-xl px-6 text-base font-semibold outline-none transition-opacity hover:opacity-90 focus-visible:ring-[3px]\">Search</button>\n            </form>\n            <p className=\"text-muted-foreground mt-4 text-sm\">Popular: {[\"Retries\", \"Dead letters\", \"Idempotency\"].map((t, i) => <React.Fragment key={t}>{i > 0 && \", \"}<a href={`${link.search}?q=${t.toLowerCase()}`} className=\"text-foreground underline underline-offset-4\">{t}</a></React.Fragment>)}</p>\n            <div className=\"mt-14\"><Flow /></div>\n          </div>\n        </section>\n\n        <section aria-labelledby=\"dh-paths\" className=\"mx-auto max-w-6xl px-4 py-16 sm:px-6\">\n          <h2 id=\"dh-paths\" className=\"sr-only\">Where to start</h2>\n          <ul className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n            {paths.map((p) => (\n              <li key={p.title}>\n                <a href={link[p.key]} className=\"bg-card hover:border-primary/50 focus-visible:ring-ring/50 group flex h-full flex-col rounded-2xl border p-6 outline-none transition-[border-color,transform] hover:-translate-y-0.5 focus-visible:ring-[3px] motion-reduce:transition-none motion-reduce:hover:translate-y-0\">\n                  <span className={cn(\"inline-flex size-11 items-center justify-center rounded-xl\", p.tint)}><p.icon className=\"size-5\" aria-hidden=\"true\" /></span>\n                  <span className={cn(\"mt-5 text-2xl\", docsSerifClass)}>{p.title}</span>\n                  <span className=\"text-muted-foreground mt-2 flex-1 text-sm text-pretty\">{p.body}</span>\n                  <span className=\"text-primary mt-5 inline-flex items-center gap-1 text-sm font-semibold\">Open <ArrowRight className=\"size-4 transition-transform group-hover:translate-x-1 motion-reduce:transition-none rtl:rotate-180 rtl:group-hover:-translate-x-1\" aria-hidden=\"true\" /></span>\n                </a>\n              </li>\n            ))}\n          </ul>\n        </section>\n\n        <section aria-labelledby=\"dh-install\" className=\"bg-surface border-y\">\n          <div className=\"mx-auto grid max-w-6xl items-center gap-10 px-4 py-16 sm:px-6 lg:grid-cols-[1fr_1.1fr]\">\n            <div>\n              <h2 id=\"dh-install\" className={cn(\"text-4xl text-balance sm:text-5xl\", docsSerifClass)}>One command, then a message.</h2>\n              <p className=\"text-muted-foreground mt-4 text-lg text-pretty\">Official SDKs for Node, Python and Go, and a plain HTTP API for everything else. Pick a language once and every code sample in the docs follows you.</p>\n              <ul className=\"mt-6 grid gap-2 text-sm\">{[\"Retries and backoff built in\", \"Typed clients, generated from the API\", \"Same behaviour in every language\"].map((t) => <li key={t} className=\"flex items-center gap-2\"><span className=\"bg-chart-4 size-1.5 rounded-full\" aria-hidden=\"true\" />{t}</li>)}</ul>\n            </div>\n            <SnippetTabs storageKey=\"tern-lang\" title=\"Install\" snippets={(Object.entries(snippets.install) as [string, string][]).map(([label, code]) => ({ label, code, language: \"bash\" }))} />\n          </div>\n        </section>\n\n        <section aria-labelledby=\"dh-pop\" className=\"mx-auto grid max-w-6xl gap-12 px-4 py-16 sm:px-6 lg:grid-cols-2\">\n          <div>\n            <h2 id=\"dh-pop\" className={cn(\"text-3xl\", docsSerifClass)}>Most read</h2>\n            <ol className=\"mt-5 divide-y border-y\">\n              {searchIndex.filter((e) => e.kind === \"Guide\").slice(0, 5).map((e, i) => (\n                <li key={e.title}>\n                  <a href={link[e.page]} className=\"hover:bg-accent focus-visible:ring-ring/50 group grid grid-cols-[2rem_1fr_auto] items-center gap-3 px-2 py-4 outline-none focus-visible:ring-[3px] focus-visible:ring-inset\">\n                    <span className={cn(\"text-muted-foreground text-sm\", docsMonoClass)}>{String(i + 1).padStart(2, \"0\")}</span>\n                    <span className=\"min-w-0\"><span className=\"block font-semibold\">{e.title}</span><span className=\"text-muted-foreground block truncate text-sm\">{e.summary}</span></span>\n                    <ArrowRight className=\"text-muted-foreground size-4 transition-transform group-hover:translate-x-1 motion-reduce:transition-none rtl:rotate-180 rtl:group-hover:-translate-x-1\" aria-hidden=\"true\" />\n                  </a>\n                </li>\n              ))}\n            </ol>\n          </div>\n          <div>\n            <div className=\"flex items-end justify-between\"><h2 className={cn(\"text-3xl\", docsSerifClass)}>What’s new</h2><a href={link.changelog} className=\"text-primary text-sm font-semibold underline-offset-4 hover:underline\">All releases</a></div>\n            <ul className=\"mt-5 grid gap-4\">\n              {releases.slice(0, 3).map((r) => (\n                <li key={r.version} className=\"bg-card rounded-2xl border p-5\">\n                  <div className=\"flex items-center gap-3\"><span className={cn(\"bg-secondary rounded-md px-2 py-0.5 text-xs font-bold\", docsMonoClass)}>{r.version}</span><span className=\"text-muted-foreground text-xs\">{new Date(r.date + \"T00:00:00Z\").toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\", year: \"numeric\", timeZone: \"UTC\" })}</span></div>\n                  <h3 className=\"mt-3 text-lg font-semibold\">{r.title}</h3>\n                  <p className=\"text-muted-foreground mt-1 text-sm text-pretty\">{r.body}</p>\n                </li>\n              ))}\n            </ul>\n          </div>\n        </section>\n\n        <section aria-labelledby=\"dh-help\" className=\"mx-auto max-w-6xl px-4 pb-8 sm:px-6\">\n          <div className=\"bg-primary text-primary-foreground flex flex-col items-start justify-between gap-6 rounded-3xl p-8 sm:flex-row sm:items-center sm:p-12\">\n            <div><h2 id=\"dh-help\" className={cn(\"text-3xl sm:text-4xl\", docsSerifClass)}>Stuck? A human will answer.</h2><p className=\"mt-2 max-w-lg text-pretty\">Average first reply on weekdays is eleven minutes. Bring your message id and we will find it.</p></div>\n            <a href={link.search} className=\"bg-primary-foreground text-primary focus-visible:ring-ring inline-flex h-12 shrink-0 items-center gap-2 rounded-xl px-6 font-semibold outline-none focus-visible:ring-[3px]\">Ask support <ArrowRight className=\"size-4 rtl:rotate-180\" aria-hidden=\"true\" /></a>\n          </div>\n        </section>\n      </main>\n    </DocsShell>\n  )\n}\n\nexport { DocsHome, type DocsHomeProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-home.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/docs/docs-guide.tsx",
      "content": "// Ballmac UI: Docs guide page. https://ui.ballmac.com/templates/template-docs\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowLeft, ArrowRight, Check, Clock, ThumbsDown, ThumbsUp } from \"lucide-react\"\n\nimport { Callout } from \"@/components/ballmac/callout\"\nimport { CodeBlock } from \"@/components/ballmac/code-block\"\nimport { CopyButton } from \"@/components/ballmac/copy-button\"\nimport { InstallTabs } from \"@/components/ballmac/install-tabs\"\nimport { SnippetTabs } from \"@/components/ballmac/snippet-tabs\"\nimport { TableOfContents } from \"@/components/ballmac/table-of-contents\"\nimport { Crumbs, DocsShell, docsMonoClass, docsSerifClass, type DocsHrefs } from \"@/components/ballmac/templates/docs/docs-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst toc = [\n  { id: \"before\", title: \"Before you begin\", level: 2 },\n  { id: \"install\", title: \"Install the SDK\", level: 2 },\n  { id: \"create\", title: \"Create a queue\", level: 2 },\n  { id: \"send\", title: \"Send a message\", level: 2 },\n  { id: \"receive\", title: \"Receive and acknowledge\", level: 2 },\n  { id: \"next\", title: \"Next steps\", level: 2 },\n]\n\nconst createSnippets = [\n  { label: \"Node\", language: \"typescript\" as const, code: `const { Tern } = require(\"@tern/sdk\")\\n\\nconst tern = new Tern({ apiKey: process.env.TERN_KEY, region: \"eu\" })\\n\\nawait tern.queues.create({\\n  name: \"invoices\",\\n  visibilityTimeout: 60,\\n})` },\n  { label: \"Python\", language: \"python\" as const, code: `import os\\nfrom tern import Tern\\n\\ntern = Tern(api_key=os.environ[\"TERN_KEY\"], region=\"eu\")\\n\\ntern.queues.create(\\n    name=\"invoices\",\\n    visibility_timeout=60,\\n)` },\n  { label: \"Go\", language: \"go\" as const, code: `client := tern.New(os.Getenv(\"TERN_KEY\"), tern.WithRegion(\"eu\"))\\n\\n_, err := client.Queues.Create(ctx, tern.CreateQueue{\\n    Name:              \"invoices\",\\n    VisibilityTimeout: 60,\\n})` },\n  { label: \"cURL\", language: \"bash\" as const, code: `curl https://eu.api.tern.dev/v1/queues \\\\\\n  -H \"Authorization: Bearer $TERN_KEY\" \\\\\\n  -d '{\"name\":\"invoices\",\"visibilityTimeout\":60}'` },\n]\nconst sendSnippets = [\n  { label: \"Node\", language: \"typescript\" as const, code: `const message = await tern.queue(\"invoices\").send(\\n  { invoice: \"inv_2041\", total: 4200 },\\n  { idempotencyKey: \"inv_2041\" }\\n)\\n\\nconsole.log(message.id) // msg_7Hq2Zc` },\n  { label: \"Python\", language: \"python\" as const, code: `message = tern.queue(\"invoices\").send(\\n    {\"invoice\": \"inv_2041\", \"total\": 4200},\\n    idempotency_key=\"inv_2041\",\\n)\\n\\nprint(message.id)  # msg_7Hq2Zc` },\n  { label: \"Go\", language: \"go\" as const, code: `msg, _ := client.Queue(\"invoices\").Send(ctx,\\n    map[string]any{\"invoice\": \"inv_2041\", \"total\": 4200},\\n    tern.IdempotencyKey(\"inv_2041\"))\\n\\nfmt.Println(msg.ID) // msg_7Hq2Zc` },\n  { label: \"cURL\", language: \"bash\" as const, code: `curl https://eu.api.tern.dev/v1/queues/invoices/messages \\\\\\n  -H \"Authorization: Bearer $TERN_KEY\" \\\\\\n  -H \"Idempotency-Key: inv_2041\" \\\\\\n  -d '{\"body\":{\"invoice\":\"inv_2041\",\"total\":4200}}'` },\n]\nconst consumeSnippets = [\n  { label: \"Node\", language: \"typescript\" as const, code: `for await (const msg of tern.queue(\"invoices\").consume({ wait: 20 })) {\\n  await chargeCustomer(msg.body)\\n  await msg.ack() // delete it, or it comes back\\n}` },\n  { label: \"Python\", language: \"python\" as const, code: `for msg in tern.queue(\"invoices\").consume(wait=20):\\n    charge_customer(msg.body)\\n    msg.ack()  # delete it, or it comes back` },\n  { label: \"Go\", language: \"go\" as const, code: `for msg := range client.Queue(\"invoices\").Consume(ctx, tern.Wait(20)) {\\n    chargeCustomer(msg.Body)\\n    msg.Ack() // delete it, or it comes back\\n}` },\n  { label: \"cURL\", language: \"bash\" as const, code: `curl \"https://eu.api.tern.dev/v1/queues/invoices/messages?wait=20\" \\\\\\n  -H \"Authorization: Bearer $TERN_KEY\"` },\n]\n\nfunction Step({ n, title, id, children, last }: { n: number; title: string; id: string; children: React.ReactNode; last?: boolean }) {\n  return (\n    <section aria-labelledby={id} className=\"grid grid-cols-[2.5rem_minmax(0,1fr)] gap-x-4 sm:gap-x-5\">\n      <div className=\"flex flex-col items-center\">\n        <span aria-hidden=\"true\" className={cn(\"bg-primary text-primary-foreground flex size-10 items-center justify-center rounded-full text-sm font-bold\", docsMonoClass)}>{n}</span>\n        {!last && <span aria-hidden=\"true\" className=\"bg-border mt-2 w-px flex-1\" />}\n      </div>\n      <div className={cn(\"grid min-w-0 gap-4\", !last && \"pb-12\")}>\n        <h2 id={id} className={cn(\"scroll-mt-24 pt-1 text-3xl\", docsSerifClass)}>{title}</h2>\n        {children}\n      </div>\n    </section>\n  )\n}\n\nfunction Helpful() {\n  const [vote, setVote] = React.useState<\"yes\" | \"no\" | null>(null)\n  const [note, setNote] = React.useState(\"\")\n  const [sent, setSent] = React.useState(false)\n  const btn = \"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:border-primary aria-pressed:bg-accent inline-flex h-10 items-center gap-2 rounded-lg border px-4 text-sm font-semibold outline-none focus-visible:ring-[3px]\"\n  return (\n    <section aria-labelledby=\"dg-help\" className=\"bg-surface rounded-2xl border p-6\">\n      <h2 id=\"dg-help\" className=\"font-semibold\">Was this page helpful?</h2>\n      {sent || vote === \"yes\" ? (\n        <p role=\"status\" className=\"mt-3 flex items-center gap-2 text-sm\"><Check className=\"text-chart-4 size-4\" aria-hidden=\"true\" />{sent ? \"Thanks. We read every note.\" : \"Glad it helped. Thank you.\"}</p>\n      ) : (\n        <>\n          <div className=\"mt-3 flex gap-2\">\n            <button type=\"button\" className={btn} onClick={() => setVote(\"yes\")}><ThumbsUp className=\"size-4\" aria-hidden=\"true\" />Yes</button>\n            <button type=\"button\" className={btn} aria-pressed={vote === \"no\"} onClick={() => setVote(\"no\")}><ThumbsDown className=\"size-4\" aria-hidden=\"true\" />No</button>\n          </div>\n          {vote === \"no\" && (\n            <form className=\"mt-4 grid gap-3\" onSubmit={(e) => { e.preventDefault(); setSent(true) }}>\n              <label htmlFor=\"dg-note\" className=\"text-sm font-medium\">What was missing or confusing?</label>\n              <textarea id=\"dg-note\" value={note} onChange={(e) => setNote(e.target.value)} rows={3} className=\"bg-background focus-visible:ring-ring/50 w-full rounded-lg border p-3 text-sm outline-none focus-visible:ring-[3px]\" />\n              <button type=\"submit\" className=\"bg-primary text-primary-foreground focus-visible:ring-ring/50 h-10 w-fit rounded-lg px-5 text-sm font-semibold outline-none focus-visible:ring-[3px]\">Send feedback</button>\n            </form>\n          )}\n        </>\n      )}\n    </section>\n  )\n}\n\ntype DocsGuideProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<DocsHrefs> }\n\n/** A Docs guide: breadcrumbs, numbered steps with synced language tabs, callouts, an on-this-page list and a feedback box. */\nfunction DocsGuide({ hrefs, ...props }: DocsGuideProps) {\n  const link = { guide: \"/docs/guides\", reference: \"/docs/reference\", ...hrefs }\n  return (\n    <DocsShell page=\"guide\" hrefs={hrefs} {...props}>\n      <div className=\"grid grid-cols-[minmax(0,1fr)] gap-12 px-4 py-10 sm:px-8 xl:grid-cols-[minmax(0,1fr)_14rem]\">\n        <main className=\"mx-auto w-full min-w-0 max-w-3xl\">\n          <Crumbs items={[\"Get started\", \"Send your first message\"]} />\n          <h1 className={cn(\"mt-5 text-[clamp(2.4rem,5vw,3.8rem)] leading-[1.05] text-balance\", docsSerifClass)}>Send your first message</h1>\n          <p className=\"text-muted-foreground mt-4 text-xl text-pretty\">Create a queue, put a message on it and take it off again. By the end you will have seen the whole loop that everything else is built on.</p>\n          <p className=\"text-muted-foreground mt-5 flex items-center gap-4 text-sm\"><span className=\"inline-flex items-center gap-1.5\"><Clock className=\"size-4\" aria-hidden=\"true\" />7 min read</span><span>Updated Sep 24, 2026</span></p>\n\n          <div className=\"mt-10\">\n            <Step n={1} id=\"before\" title=\"Before you begin\">\n              <p className=\"text-pretty\">You need a Tern account and a test API key. Test keys only touch test queues, so nothing here can affect production.</p>\n              <div className=\"bg-card flex items-center justify-between gap-3 rounded-xl border p-3 ps-4\">\n                <div className=\"min-w-0\"><p className=\"text-muted-foreground text-xs\">Your test key</p><p className={cn(\"truncate text-sm\", docsMonoClass)}>tern_test_4fJ2kQ9xN7mB1c</p></div>\n                <CopyButton value=\"tern_test_4fJ2kQ9xN7mB1c\" ariaLabel=\"Copy the test key\" variant=\"outline\" size=\"sm\" />\n              </div>\n              <Callout kind=\"note\" title=\"Pick a region once\">Queues live in one region. Use <code className={docsMonoClass}>eu</code>, <code className={docsMonoClass}>us</code> or <code className={docsMonoClass}>ap</code> and keep it the same in every client.</Callout>\n            </Step>\n            <Step n={2} id=\"install\" title=\"Install the SDK\">\n              <p className=\"text-pretty\">Add the Node package with your package manager. Using Python or Go? Switch the tabs below and every sample on this page follows.</p>\n              <InstallTabs storageKey=\"tern-pm\" commands={{ npm: \"npm install @tern/sdk\", pnpm: \"pnpm add @tern/sdk\", yarn: \"yarn add @tern/sdk\", bun: \"bun add @tern/sdk\" }} />\n            </Step>\n            <Step n={3} id=\"create\" title=\"Create a queue\">\n              <p className=\"text-pretty\">A queue holds messages until a consumer acknowledges them. The visibility timeout is how long a received message stays hidden before Tern offers it to someone else.</p>\n              <SnippetTabs storageKey=\"tern-lang\" title=\"Create a queue\" lineNumbers snippets={createSnippets} />\n              <Callout kind=\"tip\" title=\"Names are permanent\">A queue cannot be renamed. Pick something you will still like in a year, such as <code className={docsMonoClass}>invoices</code> rather than <code className={docsMonoClass}>test2</code>.</Callout>\n            </Step>\n            <Step n={4} id=\"send\" title=\"Send a message\">\n              <p className=\"text-pretty\">The body can be any JSON up to 256 KB. Pass an idempotency key and a network retry returns the first message instead of adding a second.</p>\n              <SnippetTabs storageKey=\"tern-lang\" title=\"Send a message\" lineNumbers snippets={sendSnippets} />\n              <CodeBlock filename=\"response.json\" language=\"json\" code={`{\\n  \"id\": \"msg_7Hq2Zc\",\\n  \"queue\": \"invoices\",\\n  \"visibleAt\": \"2026-09-30T08:14:22Z\"\\n}`} highlight={[2]} />\n            </Step>\n            <Step n={5} id=\"receive\" title=\"Receive and acknowledge\" last>\n              <p className=\"text-pretty\">Consuming is a loop: wait for a message, do the work, then acknowledge. Acknowledging deletes the message. If your code crashes first, the visibility timeout ends and Tern hands the message to the next consumer.</p>\n              <SnippetTabs storageKey=\"tern-lang\" title=\"Consume messages\" lineNumbers snippets={consumeSnippets} />\n              <Callout kind=\"caution\" title=\"Acknowledge only after the work succeeds\">Delivery is at least once. A message can arrive twice, so make your handler safe to run twice. The idempotent consumers guide shows how.</Callout>\n            </Step>\n          </div>\n\n          <section aria-labelledby=\"next\" className=\"mt-14\">\n            <h2 id=\"next\" className={cn(\"scroll-mt-24 text-3xl\", docsSerifClass)}>Next steps</h2>\n            <ul className=\"mt-5 grid gap-4 sm:grid-cols-2\">\n              {[[\"Delivery guarantees\", \"What at-least-once really means for your code.\", link.guide], [\"Dead-letter queues\", \"Park messages that keep failing and replay them later.\", link.guide], [\"Idempotent consumers\", \"Make retries safe.\", link.guide], [\"API reference\", \"Every endpoint, with examples.\", link.reference]].map(([t, d, h]) => (\n                <li key={t}><a href={h} className=\"bg-card hover:border-primary/50 focus-visible:ring-ring/50 group block h-full rounded-xl border p-5 outline-none transition-colors focus-visible:ring-[3px] motion-reduce:transition-none\"><span className=\"flex items-center justify-between font-semibold\">{t}<ArrowRight className=\"size-4 transition-transform group-hover:translate-x-1 motion-reduce:transition-none rtl:rotate-180 rtl:group-hover:-translate-x-1\" aria-hidden=\"true\" /></span><span className=\"text-muted-foreground mt-1 block text-sm\">{d}</span></a></li>\n              ))}\n            </ul>\n          </section>\n\n          <div className=\"mt-12\"><Helpful /></div>\n\n          <nav aria-label=\"Previous and next page\" className=\"mt-8 grid gap-4 sm:grid-cols-2\">\n            <a href={link.guide} className=\"hover:border-primary/50 focus-visible:ring-ring/50 group rounded-xl border p-5 outline-none transition-colors focus-visible:ring-[3px] motion-reduce:transition-none\"><span className=\"text-muted-foreground flex items-center gap-1 text-xs\"><ArrowLeft className=\"size-3.5 rtl:rotate-180\" aria-hidden=\"true\" />Previous</span><span className=\"mt-1 block font-semibold\">Core concepts</span></a>\n            <a href={link.guide} className=\"hover:border-primary/50 focus-visible:ring-ring/50 group rounded-xl border p-5 text-end outline-none transition-colors focus-visible:ring-[3px] motion-reduce:transition-none\"><span className=\"text-muted-foreground flex items-center justify-end gap-1 text-xs\">Next<ArrowRight className=\"size-3.5 rtl:rotate-180\" aria-hidden=\"true\" /></span><span className=\"mt-1 block font-semibold\">Delivery guarantees</span></a>\n          </nav>\n        </main>\n        <aside className=\"hidden xl:block\" aria-label=\"On this page\"><div className=\"sticky top-24\"><TableOfContents items={toc} title=\"On this page\" offset={96} /></div></aside>\n      </div>\n    </DocsShell>\n  )\n}\n\nexport { DocsGuide, type DocsGuideProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-guide.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/docs/docs-reference.tsx",
      "content": "// Ballmac UI: Docs API reference page. https://ui.ballmac.com/templates/template-docs\n\"use client\"\n\nimport * as React from \"react\"\nimport { Search } from \"lucide-react\"\n\nimport { ApiEndpoint } from \"@/components/ballmac/api-endpoint\"\nimport { Callout } from \"@/components/ballmac/callout\"\nimport { CopyButton } from \"@/components/ballmac/copy-button\"\nimport { TableOfContents } from \"@/components/ballmac/table-of-contents\"\nimport { endpointGroups } from \"@/components/ballmac/templates/docs/docs-data\"\nimport { Crumbs, DocsShell, docsMonoClass, docsSerifClass, type DocsHrefs } from \"@/components/ballmac/templates/docs/docs-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst methods = [\"All\", \"GET\", \"POST\", \"DELETE\"] as const\nconst limits = [[\"Requests\", \"600 per minute per key\"], [\"Message size\", \"256 KB\"], [\"Batch receive\", \"50 messages\"], [\"Long poll\", \"20 seconds\"], [\"Idempotency keys\", \"24 hours\"]]\nconst toc = [\n  { id: \"auth\", title: \"Authentication\", level: 2 },\n  { id: \"limits\", title: \"Limits\", level: 2 },\n  { id: \"queues\", title: \"Queues\", level: 2 },\n  { id: \"messages\", title: \"Messages\", level: 2 },\n]\n\ntype DocsReferenceProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<DocsHrefs> }\n\n/** The API reference: authentication, limits and expandable endpoint cards that filter by method or text. */\nfunction DocsReference({ hrefs, ...props }: DocsReferenceProps) {\n  const [method, setMethod] = React.useState<(typeof methods)[number]>(\"All\")\n  const [query, setQuery] = React.useState(\"\")\n  const q = query.trim().toLowerCase()\n  const groups = endpointGroups\n    .map((g) => ({ ...g, endpoints: g.endpoints.filter((e) => (method === \"All\" || e.method === method) && (!q || `${e.summary} ${e.path} ${e.description}`.toLowerCase().includes(q))) }))\n    .filter((g) => g.endpoints.length > 0)\n  const total = groups.reduce((n, g) => n + g.endpoints.length, 0)\n  return (\n    <DocsShell page=\"reference\" hrefs={hrefs} {...props}>\n      <div className=\"grid grid-cols-[minmax(0,1fr)] gap-12 px-4 py-10 sm:px-8 xl:grid-cols-[minmax(0,1fr)_14rem]\">\n        <main className=\"mx-auto w-full min-w-0 max-w-3xl\">\n          <Crumbs items={[\"API reference\", \"Overview\"]} />\n          <h1 className={cn(\"mt-5 text-[clamp(2.4rem,5vw,3.8rem)] leading-[1.05] text-balance\", docsSerifClass)}>API reference</h1>\n          <p className=\"text-muted-foreground mt-4 text-xl text-pretty\">A small, regular HTTP API. Every endpoint takes and returns JSON, and every SDK method maps to exactly one of them.</p>\n          <div className=\"bg-card mt-6 flex items-center justify-between gap-3 rounded-xl border p-3 ps-4\">\n            <div className=\"min-w-0\"><p className=\"text-muted-foreground text-xs\">Base URL</p><p className={cn(\"truncate text-sm\", docsMonoClass)}>https://eu.api.tern.dev</p></div>\n            <CopyButton value=\"https://eu.api.tern.dev\" ariaLabel=\"Copy the base URL\" variant=\"outline\" size=\"sm\" />\n          </div>\n\n          <section aria-labelledby=\"auth\" className=\"mt-12\">\n            <h2 id=\"auth\" className={cn(\"scroll-mt-24 text-3xl\", docsSerifClass)}>Authentication</h2>\n            <p className=\"mt-3 text-pretty\">Send your key as a bearer token. Test keys start with <code className={docsMonoClass}>tern_test_</code> and live keys with <code className={docsMonoClass}>tern_live_</code>.</p>\n            <pre tabIndex={0} role=\"region\" aria-label=\"Authorization header\" className={cn(\"bg-muted/60 mt-4 overflow-x-auto rounded-xl border p-4 text-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\", docsMonoClass)}>Authorization: Bearer tern_live_••••••••</pre>\n            <div className=\"mt-4\"><Callout kind=\"caution\" title=\"Keep live keys on the server\">Never ship a live key in a browser or a mobile app. Create a restricted key that can only send messages if a client must call Tern directly.</Callout></div>\n          </section>\n\n          <section aria-labelledby=\"limits\" className=\"mt-12\">\n            <h2 id=\"limits\" className={cn(\"scroll-mt-24 text-3xl\", docsSerifClass)}>Limits</h2>\n            <dl className=\"mt-4 divide-y rounded-xl border\">\n              {limits.map(([k, v]) => <div key={k} className=\"grid grid-cols-2 gap-4 px-4 py-3 text-sm\"><dt className=\"text-muted-foreground\">{k}</dt><dd className={cn(\"font-medium\", docsMonoClass)}>{v}</dd></div>)}\n            </dl>\n          </section>\n\n          <div className=\"mt-14 flex flex-wrap items-center gap-3\" role=\"group\" aria-label=\"Filter endpoints\">\n            <div className=\"relative min-w-52 flex-1\">\n              <label htmlFor=\"dr-q\" className=\"sr-only\">Filter endpoints</label>\n              <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-3 size-4 -translate-y-1/2\" aria-hidden=\"true\" />\n              <input id=\"dr-q\" type=\"search\" value={query} onChange={(e) => setQuery(e.target.value)} placeholder=\"Filter endpoints\" className=\"bg-card focus-visible:ring-ring/50 h-10 w-full rounded-lg border pe-3 ps-9 text-sm outline-none focus-visible:ring-[3px]\" />\n            </div>\n            <div className=\"flex gap-1.5\">\n              {methods.map((m) => (\n                <button key={m} type=\"button\" aria-pressed={method === m} onClick={() => setMethod(m)} className={cn(\"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:bg-primary aria-pressed:text-primary-foreground h-10 rounded-lg border px-3 text-xs font-bold outline-none focus-visible:ring-[3px]\", docsMonoClass)}>{m}</button>\n              ))}\n            </div>\n          </div>\n          <p role=\"status\" className=\"text-muted-foreground mt-3 text-sm\">{total} {total === 1 ? \"endpoint\" : \"endpoints\"}</p>\n\n          {groups.map((g) => (\n            <section key={g.title} aria-labelledby={g.title.toLowerCase()} className=\"mt-8\">\n              <h2 id={g.title.toLowerCase()} className={cn(\"scroll-mt-24 text-3xl\", docsSerifClass)}>{g.title}</h2>\n              <div className=\"mt-5 grid gap-4\">\n                {g.endpoints.map((e, i) => (\n                  <ApiEndpoint key={e.method + e.path} method={e.method} path={e.path} summary={e.summary} description={e.description} baseUrl=\"https://eu.api.tern.dev\" auth=\"Bearer key\" parameters={e.parameters} requestExample={e.requestExample} responses={e.responses} defaultOpen={i === 0 && g.title === groups[0]?.title} />\n                ))}\n              </div>\n            </section>\n          ))}\n          {total === 0 && <div className=\"bg-surface mt-8 rounded-2xl border p-10 text-center\"><p className={cn(\"text-2xl\", docsSerifClass)}>No endpoint matches “{query}”.</p><p className=\"text-muted-foreground mt-2 text-sm\">Try a shorter word, or clear the method filter.</p><button type=\"button\" onClick={() => { setQuery(\"\"); setMethod(\"All\") }} className=\"bg-primary text-primary-foreground focus-visible:ring-ring/50 mt-5 h-10 rounded-lg px-5 text-sm font-semibold outline-none focus-visible:ring-[3px]\">Clear filters</button></div>}\n        </main>\n        <aside className=\"hidden xl:block\" aria-label=\"On this page\"><div className=\"sticky top-24\"><TableOfContents items={toc} title=\"On this page\" offset={96} /></div></aside>\n      </div>\n    </DocsShell>\n  )\n}\n\nexport { DocsReference, type DocsReferenceProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-reference.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/docs/docs-search.tsx",
      "content": "// Ballmac UI: Docs search page. https://ui.ballmac.com/templates/template-docs\n\"use client\"\n\nimport * as React from \"react\"\nimport { Search } from \"lucide-react\"\n\nimport { searchIndex, type DocsKind } from \"@/components/ballmac/templates/docs/docs-data\"\nimport { DocsShell, docsKindStyle, docsMonoClass, docsSerifClass, type DocsHrefs } from \"@/components/ballmac/templates/docs/docs-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst kinds = [\"All\", \"Guide\", \"API\", \"Changelog\"] as const\nconst suggestions = [\"retry\", \"idempotency\", \"dead letter\", \"visibility\", \"region\"]\n\nfunction Marked({ text, query }: { text: string; query: string }) {\n  const terms = query.trim().split(/\\s+/).filter(Boolean).map((t) => t.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\"))\n  if (terms.length === 0) return <>{text}</>\n  const parts = text.split(new RegExp(`(${terms.join(\"|\")})`, \"ig\"))\n  return <>{parts.map((p, i) => (i % 2 === 1 ? <mark key={i} className=\"bg-chart-3/35 text-foreground rounded-sm px-0.5\">{p}</mark> : <React.Fragment key={i}>{p}</React.Fragment>))}</>\n}\n\ntype DocsSearchProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<DocsHrefs>; initialQuery?: string }\n\n/** A full search page: filter by type, matches highlighted in titles and summaries, helpful suggestions when nothing matches. */\nfunction DocsSearch({ hrefs, initialQuery = \"message\", ...props }: DocsSearchProps) {\n  const [query, setQuery] = React.useState(initialQuery)\n  const [kind, setKind] = React.useState<(typeof kinds)[number]>(\"All\")\n  const link = { guide: \"/docs/guides\", reference: \"/docs/reference\", changelog: \"/docs/changelog\", ...hrefs }\n  const terms = query.trim().toLowerCase().split(/\\s+/).filter(Boolean)\n  const all = searchIndex.filter((e) => terms.every((t) => `${e.title} ${e.summary} ${e.path}`.toLowerCase().includes(t)))\n  const results = all.filter((e) => kind === \"All\" || e.kind === kind)\n  const count = (k: DocsKind) => all.filter((e) => e.kind === k).length\n  return (\n    <DocsShell page=\"search\" hrefs={hrefs} sidebar={false} {...props}>\n      <main className=\"mx-auto max-w-3xl px-4 py-14 sm:px-6 sm:py-20\">\n        <h1 className={cn(\"text-[clamp(2.4rem,6vw,4rem)] leading-none\", docsSerifClass)}>Search</h1>\n        <form role=\"search\" onSubmit={(e) => e.preventDefault()} className=\"mt-8\">\n          <label htmlFor=\"ds-q\" className=\"sr-only\">Search the docs</label>\n          <div className=\"bg-card focus-within:ring-ring/50 relative rounded-xl border shadow-sm focus-within:ring-[3px]\">\n            <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 start-4 size-5 -translate-y-1/2\" aria-hidden=\"true\" />\n            <input id=\"ds-q\" type=\"search\" value={query} onChange={(e) => setQuery(e.target.value)} placeholder=\"Search guides, endpoints and releases\" className=\"h-14 w-full rounded-xl bg-transparent pe-4 ps-12 text-base outline-none\" />\n          </div>\n        </form>\n        <div className=\"mt-5 flex flex-wrap gap-2\" role=\"group\" aria-label=\"Filter by type\">\n          {kinds.map((k) => (\n            <button key={k} type=\"button\" aria-pressed={kind === k} onClick={() => setKind(k)} className=\"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:bg-primary aria-pressed:text-primary-foreground inline-flex h-9 items-center gap-2 rounded-full border px-4 text-sm font-semibold outline-none focus-visible:ring-[3px]\">\n              {k === \"API\" ? \"API reference\" : k === \"Guide\" ? \"Guides\" : k === \"Changelog\" ? \"Releases\" : \"Everything\"}\n              {k !== \"All\" && <span className={cn(\"text-xs opacity-100\", docsMonoClass)}>{count(k)}</span>}\n            </button>\n          ))}\n        </div>\n        <p role=\"status\" className=\"text-muted-foreground mt-6 text-sm\">{results.length} {results.length === 1 ? \"result\" : \"results\"}{query.trim() ? <> for “{query.trim()}”</> : null}</p>\n        {results.length > 0 ? (\n          <ul className=\"mt-2 divide-y\">\n            {results.map((e) => (\n              <li key={e.title}>\n                <a href={link[e.page]} className=\"hover:bg-accent focus-visible:ring-ring/50 -mx-3 block rounded-xl px-3 py-5 outline-none focus-visible:ring-[3px]\">\n                  <span className=\"flex items-center gap-2 text-xs\">\n                    <span className={cn(\"rounded-md px-2 py-0.5 font-bold\", docsKindStyle[e.kind])}>{e.kind === \"API\" ? \"API\" : e.kind}</span>\n                    <span className={cn(\"text-muted-foreground truncate\", e.kind === \"API\" && docsMonoClass)}>{e.path}</span>\n                  </span>\n                  <span className={cn(\"mt-2 block text-2xl\", docsSerifClass)}><Marked text={e.title} query={query} /></span>\n                  <span className=\"text-muted-foreground mt-1 block text-pretty\"><Marked text={e.summary} query={query} /></span>\n                </a>\n              </li>\n            ))}\n          </ul>\n        ) : (\n          <div className=\"bg-surface mt-4 rounded-2xl border p-10 text-center\">\n            <p className={cn(\"text-3xl\", docsSerifClass)}>Nothing for “{query.trim()}”.</p>\n            <p className=\"text-muted-foreground mt-2\">Check the spelling, try fewer words or start from one of these.</p>\n            <div className=\"mt-6 flex flex-wrap justify-center gap-2\">{suggestions.map((s) => <button key={s} type=\"button\" onClick={() => { setQuery(s); setKind(\"All\") }} className=\"bg-card hover:bg-accent focus-visible:ring-ring/50 h-9 rounded-full border px-4 text-sm font-semibold outline-none focus-visible:ring-[3px]\">{s}</button>)}</div>\n          </div>\n        )}\n      </main>\n    </DocsShell>\n  )\n}\n\nexport { DocsSearch, type DocsSearchProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-search.tsx"
    },
    {
      "path": "registry/ballmac/components/templates/docs/docs-changelog.tsx",
      "content": "// Ballmac UI: Docs changelog page. https://ui.ballmac.com/templates/template-docs\n\"use client\"\n\nimport * as React from \"react\"\nimport { Rss } from \"lucide-react\"\n\nimport { releases, type Release } from \"@/components/ballmac/templates/docs/docs-data\"\nimport { DocsShell, docsMonoClass, docsSerifClass, type DocsHrefs } from \"@/components/ballmac/templates/docs/docs-theme\"\nimport { cn } from \"@/lib/utils\"\n\nconst tags = [\"All\", \"Added\", \"Improved\", \"Fixed\", \"Breaking\"] as const\nconst tagStyle: Record<Release[\"tag\"], string> = { Added: \"bg-chart-4/15\", Improved: \"bg-chart-2/15\", Fixed: \"bg-chart-3/20\", Breaking: \"bg-destructive/15\" }\n/** Renders `code` spans written with backticks. */\nfunction Ticks({ text }: { text: string }) {\n  return <>{text.split(\"`\").map((part, i) => (i % 2 === 1 ? <code key={i} className={cn(\"bg-muted rounded px-1 py-0.5 text-[0.85em]\", docsMonoClass)}>{part}</code> : <React.Fragment key={i}>{part}</React.Fragment>))}</>\n}\nconst fmt = (d: string) => new Date(d + \"T00:00:00Z\").toLocaleDateString(\"en-US\", { month: \"long\", day: \"numeric\", year: \"numeric\", timeZone: \"UTC\" })\n\ntype DocsChangelogProps = React.ComponentProps<\"div\"> & { hrefs?: Partial<DocsHrefs> }\n\n/** The changelog: a version rail, tag filters and each release with its upgrade notes. */\nfunction DocsChangelog({ hrefs, ...props }: DocsChangelogProps) {\n  const [tag, setTag] = React.useState<(typeof tags)[number]>(\"All\")\n  const list = releases.filter((r) => tag === \"All\" || r.tag === tag)\n  return (\n    <DocsShell page=\"changelog\" hrefs={hrefs} {...props}>\n      <div className=\"grid grid-cols-[minmax(0,1fr)] gap-12 px-4 py-10 sm:px-8 xl:grid-cols-[minmax(0,1fr)_14rem]\">\n        <main className=\"mx-auto w-full min-w-0 max-w-3xl\">\n          <h1 className={cn(\"text-[clamp(2.4rem,5vw,3.8rem)] leading-[1.05]\", docsSerifClass)}>Changelog</h1>\n          <p className=\"text-muted-foreground mt-4 text-xl text-pretty\">What shipped, what changed and what to do about it. Breaking changes always get a major version and a month of notice.</p>\n          <div className=\"mt-6 flex flex-wrap items-center gap-2\" role=\"group\" aria-label=\"Filter releases\">\n            {tags.map((t) => <button key={t} type=\"button\" aria-pressed={tag === t} onClick={() => setTag(t)} className=\"hover:bg-accent focus-visible:ring-ring/50 aria-pressed:bg-primary aria-pressed:text-primary-foreground h-9 rounded-full border px-4 text-sm font-semibold outline-none focus-visible:ring-[3px]\">{t}</button>)}\n            <a href=\"/docs/changelog.xml\" className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 ms-auto inline-flex items-center gap-1.5 rounded-md text-sm font-medium outline-none focus-visible:ring-[3px]\"><Rss className=\"size-4\" aria-hidden=\"true\" />RSS</a>\n          </div>\n          <p role=\"status\" className=\"text-muted-foreground mt-4 text-sm\">{list.length} {list.length === 1 ? \"release\" : \"releases\"}</p>\n          <ol className=\"mt-4\">\n            {list.map((r, i) => (\n              <li key={r.version} id={`v${r.version}`} className=\"relative grid scroll-mt-24 grid-cols-[1.5rem_minmax(0,1fr)] gap-x-4\">\n                <div className=\"flex flex-col items-center\"><span aria-hidden=\"true\" className={cn(\"mt-2 size-3 rounded-full border-2\", r.tag === \"Breaking\" ? \"border-destructive bg-destructive\" : \"border-primary bg-background\")} />{i < list.length - 1 && <span aria-hidden=\"true\" className=\"bg-border mt-1 w-px flex-1\" />}</div>\n                <article aria-labelledby={`r-${r.version}`} className=\"pb-12\">\n                  <div className=\"flex flex-wrap items-center gap-3 text-sm\">\n                    <span className={cn(\"bg-secondary rounded-md px-2 py-0.5 text-xs font-bold\", docsMonoClass)}>{r.version}</span>\n                    <span className={cn(\"rounded-md px-2 py-0.5 text-xs font-bold\", tagStyle[r.tag])}>{r.tag}</span>\n                    <time dateTime={r.date} className=\"text-muted-foreground\">{fmt(r.date)}</time>\n                  </div>\n                  <h2 id={`r-${r.version}`} className={cn(\"mt-3 text-3xl\", docsSerifClass)}>{r.title}</h2>\n                  <p className=\"mt-2 text-pretty\"><Ticks text={r.body} /></p>\n                  <ul className=\"mt-4 grid gap-2\">{r.points.map((p) => <li key={p} className=\"text-muted-foreground flex gap-3 text-sm\"><span className=\"bg-border mt-2 size-1.5 shrink-0 rounded-full\" aria-hidden=\"true\" /><span className=\"text-pretty\"><Ticks text={p} /></span></li>)}</ul>\n                </article>\n              </li>\n            ))}\n          </ol>\n          {list.length === 0 && <p className=\"text-muted-foreground py-10 text-center\">No releases with this tag yet.</p>}\n        </main>\n        <aside className=\"hidden xl:block\" aria-label=\"Versions\"><div className=\"sticky top-24\"><h2 className=\"text-xs font-bold tracking-[0.12em] uppercase\">Versions</h2><ul className=\"mt-3 grid gap-1 border-s text-sm\">{releases.map((r) => <li key={r.version}><a href={`#v${r.version}`} className={cn(\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 -ms-px block rounded-e-md border-s-2 border-transparent px-3 py-1 outline-none hover:border-current focus-visible:ring-[3px]\", docsMonoClass)}>{r.version}</a></li>)}</ul></div></aside>\n      </div>\n    </DocsShell>\n  )\n}\n\nexport { DocsChangelog, type DocsChangelogProps }\n",
      "type": "registry:component",
      "target": "@components/ballmac/templates/docs/docs-changelog.tsx"
    },
    {
      "path": "registry/ballmac/app/docs/page.tsx",
      "content": "// Ballmac UI: Docs template route. https://ui.ballmac.com/templates/template-docs\nimport type { Metadata } from \"next\"\n\nimport { DocsHome } from \"@/components/ballmac/templates/docs/docs-home\"\n\nexport const metadata: Metadata = {\n  title: \"Tern docs: queues that never lose a message\",\n  description: \"Guides, API reference and changelog for Tern.\",\n}\n\nexport default function Page() {\n  return <DocsHome />\n}\n",
      "type": "registry:page",
      "target": "app/docs/page.tsx"
    },
    {
      "path": "registry/ballmac/app/docs/guides/page.tsx",
      "content": "// Ballmac UI: Docs template route. https://ui.ballmac.com/templates/template-docs\nimport type { Metadata } from \"next\"\n\nimport { DocsGuide } from \"@/components/ballmac/templates/docs/docs-guide\"\n\nexport const metadata: Metadata = {\n  title: \"Send your first message · Tern docs\",\n  description: \"Create a queue, send a message and acknowledge it in about seven minutes.\",\n}\n\nexport default function Page() {\n  return <DocsGuide />\n}\n",
      "type": "registry:page",
      "target": "app/docs/guides/page.tsx"
    },
    {
      "path": "registry/ballmac/app/docs/reference/page.tsx",
      "content": "// Ballmac UI: Docs template route. https://ui.ballmac.com/templates/template-docs\nimport type { Metadata } from \"next\"\n\nimport { DocsReference } from \"@/components/ballmac/templates/docs/docs-reference\"\n\nexport const metadata: Metadata = {\n  title: \"API reference · Tern docs\",\n  description: \"Every endpoint, parameter and error code.\",\n}\n\nexport default function Page() {\n  return <DocsReference />\n}\n",
      "type": "registry:page",
      "target": "app/docs/reference/page.tsx"
    },
    {
      "path": "registry/ballmac/app/docs/search/page.tsx",
      "content": "// Ballmac UI: Docs template route. https://ui.ballmac.com/templates/template-docs\nimport type { Metadata } from \"next\"\n\nimport { DocsSearch } from \"@/components/ballmac/templates/docs/docs-search\"\n\nexport const metadata: Metadata = {\n  title: \"Search · Tern docs\",\n  description: \"Search guides, endpoints and releases.\",\n}\n\nexport default function Page() {\n  return <DocsSearch />\n}\n",
      "type": "registry:page",
      "target": "app/docs/search/page.tsx"
    },
    {
      "path": "registry/ballmac/app/docs/changelog/page.tsx",
      "content": "// Ballmac UI: Docs template route. https://ui.ballmac.com/templates/template-docs\nimport type { Metadata } from \"next\"\n\nimport { DocsChangelog } from \"@/components/ballmac/templates/docs/docs-changelog\"\n\nexport const metadata: Metadata = {\n  title: \"Changelog · Tern docs\",\n  description: \"What shipped, what changed and what to do about it.\",\n}\n\nexport default function Page() {\n  return <DocsChangelog />\n}\n",
      "type": "registry:page",
      "target": "app/docs/changelog/page.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "template",
      "docs",
      "documentation",
      "api reference",
      "search",
      "changelog",
      "developer"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "Installs a five-page documentation site with ⌘K search, synced language tabs, an on-this-page list, endpoint cards and a changelog. Change content in docs-data.ts and the palette in docsCss.",
      "whenToUse": [
        "Developer docs, API references and product help centres",
        "Any site that needs search, a guide layout and a changelog"
      ],
      "whenNotToUse": [
        "A long-form editorial site (use the publication template)",
        "Marketing pages without reference content"
      ],
      "composesWith": [
        "command",
        "snippet-tabs",
        "table-of-contents",
        "api-endpoint",
        "callout"
      ],
      "a11y": [
        {
          "keys": "⌘K / Ctrl+K",
          "action": "Opens the docs search"
        },
        {
          "keys": "Arrow keys and Enter in search",
          "action": "Move through results and open one"
        },
        {
          "keys": "Method buttons on the reference page",
          "action": "Filter endpoints; each is a pressed toggle"
        }
      ],
      "customization": [
        "Replace nav, searchIndex, releases and endpointGroups in docs-data.ts",
        "Edit docsCss for the light and dark palettes",
        "Swap the Tern mark in DocsLogo"
      ]
    },
    "examples": [
      "template-docs-demo",
      "template-docs-guide",
      "template-docs-reference",
      "template-docs-search",
      "template-docs-changelog"
    ],
    "url": "https://ui.ballmac.com/components/template-docs"
  },
  "docs": "Pages are at /docs, /docs/guides, /docs/reference, /docs/search and /docs/changelog. Edit navigation, search entries, releases and endpoints in docs-data.ts; edit docsCss in docs-theme.tsx for the palette. Language tabs share one storage key (tern-lang), so a reader's choice follows them across pages.",
  "categories": [
    "templates"
  ],
  "type": "registry:block"
}