{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-block",
  "title": "Code Block",
  "description": "Syntax-highlighted code block with line numbers and copy button.",
  "files": [
    {
      "path": "registry/blocks/code-block/code-block.tsx",
      "content": "'use client'\n\nimport { cn } from '@/lib/utils'\nimport { Check, Copy } from 'lucide-react'\nimport React, { memo, useMemo, useState } from 'react'\n\nimport { tokenize, type TokenizeContext } from '@/components/infsh/code-block/tokenizer'\nimport { tokenStyles } from '@/components/infsh/code-block/styles'\nimport { normalizeLanguage } from '@/components/infsh/code-block/languages'\nimport { getTextContent, splitLines, copyToClipboard } from '@/components/infsh/code-block/utils'\n\n// Re-export types for consumers\nexport type { Token, TokenType, LanguageDefinition } from '@/components/infsh/code-block/types'\nexport { getLanguage, normalizeLanguage } from '@/components/infsh/code-block/languages'\n\n/**\n * Tokenize all lines with context passing between lines\n * This handles multiline constructs like template literals\n */\nfunction tokenizeLines(lines: string[], language: string) {\n  const tokenizedLines: { tokens: ReturnType<typeof tokenize>['tokens'] }[] = []\n  let context: TokenizeContext = {}\n\n  for (const line of lines) {\n    const result = tokenize(line, language, context)\n    tokenizedLines.push({ tokens: result.tokens })\n    context = result.context\n  }\n\n  return tokenizedLines\n}\n\n/**\n * Renders a single line with pre-computed tokens\n */\nconst HighlightedLine = memo(function HighlightedLine({\n  tokens,\n}: {\n  tokens: ReturnType<typeof tokenize>['tokens']\n}) {\n  return (\n    <>\n      {tokens.map((token, i) =>\n        token.type ? (\n          <span key={i} className={tokenStyles[token.type]}>\n            {token.content}\n          </span>\n        ) : (\n          <span key={i}>{token.content}</span>\n        )\n      )}\n    </>\n  )\n})\n\nexport interface CodeBlockProps {\n  children: React.ReactNode\n  language?: string\n  className?: string\n  /** Show line numbers (default: true) */\n  showLineNumbers?: boolean\n  /** Show copy button (default: true) */\n  showCopyButton?: boolean\n  /** Show language header (default: true) */\n  showHeader?: boolean\n  /** Enable syntax highlighting (default: true) */\n  enableHighlighting?: boolean\n  /** Custom text size class */\n  textSize?: string\n}\n\n/**\n * Full-featured code block with syntax highlighting, line numbers, and copy button\n */\nexport const CodeBlock = memo(function CodeBlock({\n  children,\n  language,\n  className,\n  showLineNumbers = true,\n  showCopyButton = true,\n  showHeader = true,\n  enableHighlighting = true,\n  textSize,\n}: CodeBlockProps) {\n  const [copied, setCopied] = useState(false)\n\n  const text = getTextContent(children)\n  const lines = splitLines(text)\n  const normalizedLang = normalizeLanguage(language)\n\n  // Pre-tokenize all lines with context passing for multiline support\n  const tokenizedLines = useMemo(\n    () => enableHighlighting ? tokenizeLines(lines, normalizedLang) : null,\n    [lines, normalizedLang, enableHighlighting]\n  )\n\n  const handleCopy = async () => {\n    const success = await copyToClipboard(text)\n    if (success) {\n      setCopied(true)\n      setTimeout(() => setCopied(false), 1500)\n    }\n  }\n\n  return (\n    <div\n      className={cn(\n        'relative group/codeblock my-6 rounded-lg border border-border overflow-hidden bg-muted/20 min-h-0 flex flex-col',\n        className\n      )}\n    >\n      {/* Header bar */}\n      {showHeader && (\n        <div className=\"flex-none flex items-center justify-between px-2 py-2 border-b border-white/5 bg-muted\">\n          <span className=\"text-xs text-zinc-500 font-mono\">\n            {language || 'code'}\n          </span>\n          {showCopyButton && (\n            <button\n              onClick={handleCopy}\n              className=\"cursor-pointer flex items-center gap-1.5 rounded text-xs text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors\"\n              aria-label=\"Copy code\"\n            >\n              {copied ? (\n                <>\n                  <Check className=\"h-3.5 w-3.5 text-emerald-400\" />\n                  <span>copied!</span>\n                </>\n              ) : (\n                <>\n                  <Copy className=\"h-3.5 w-3.5\" />\n                  <span>copy</span>\n                </>\n              )}\n            </button>\n          )}\n        </div>\n      )}\n\n      {/* Code content */}\n      <pre className=\"flex-1 min-h-0 overflow-auto p-4\">\n        <code className={cn('grid font-mono', textSize || 'text-xs')}>\n          {lines.map((line, i) => (\n            <span\n              key={i}\n              className={cn(\n                'grid gap-6',\n                showLineNumbers ? 'grid-cols-[auto_1fr]' : 'grid-cols-1'\n              )}\n            >\n              {showLineNumbers && (\n                <span className=\"text-zinc-600 select-none text-right min-w-[2ch] text-xs leading-6\">\n                  {i + 1}\n                </span>\n              )}\n              <span className=\"whitespace-pre leading-6\">\n                {tokenizedLines ? (\n                  <HighlightedLine tokens={tokenizedLines[i]?.tokens ?? [{ type: null, content: line || ' ' }]} />\n                ) : (\n                  line || ' '\n                )}\n              </span>\n            </span>\n          ))}\n        </code>\n      </pre>\n    </div>\n  )\n})\n\nexport interface CompactCodeBlockProps {\n  children: React.ReactNode\n  className?: string\n  textSize?: string\n  /** Optional language for syntax highlighting */\n  language?: string\n}\n\n/**\n * Simpler code block for compact/chat contexts\n * Optional syntax highlighting, minimal styling\n */\nexport const CompactCodeBlock = memo(function CompactCodeBlock({\n  children,\n  className,\n  textSize = 'text-xs',\n  language,\n}: CompactCodeBlockProps) {\n  const [copied, setCopied] = useState(false)\n\n  const text = getTextContent(children)\n  const lines = splitLines(text)\n  const normalizedLang = language ? normalizeLanguage(language) : null\n\n  // Pre-tokenize if language is provided\n  const tokenizedLines = useMemo(\n    () => normalizedLang ? tokenizeLines(lines, normalizedLang) : null,\n    [lines, normalizedLang]\n  )\n\n  const handleCopy = async () => {\n    const success = await copyToClipboard(text)\n    if (success) {\n      setCopied(true)\n      setTimeout(() => setCopied(false), 1500)\n    }\n  }\n\n  return (\n    <div\n      className={cn(\n        'relative group/codeblock block bg-muted/30 rounded-lg max-w-full min-w-0',\n        className\n      )}\n    >\n      <button\n        onClick={handleCopy}\n        className=\"cursor-pointer absolute top-1 right-1 p-1.5 rounded bg-background/80 hover:bg-background border border-border opacity-0 group-hover/codeblock:opacity-100 transition-opacity z-10\"\n        aria-label=\"Copy code\"\n      >\n        {copied ? (\n          <Check className=\"h-3.5 w-3.5 text-pink-400\" />\n        ) : (\n          <Copy className=\"h-3.5 w-3.5 text-muted-foreground\" />\n        )}\n      </button>\n      <div className=\"overflow-y-auto max-h-[inherit] p-2\">\n        <pre className=\"overflow-x-auto\">\n          <code className=\"grid\">\n            {lines.map((line, i) => (\n              <span key={i} className=\"grid grid-cols-[auto_1fr] gap-4\">\n                <span\n                  className={cn(\n                    textSize,\n                    'text-muted-foreground/30 select-none text-right min-w-[2ch]'\n                  )}\n                >\n                  {i + 1}\n                </span>\n                <span\n                  className={cn(\n                    textSize,\n                    'whitespace-pre-wrap break-all',\n                    !tokenizedLines && 'text-muted-foreground'\n                  )}\n                >\n                  {tokenizedLines ? (\n                    <HighlightedLine tokens={tokenizedLines[i]?.tokens ?? [{ type: null, content: line || ' ' }]} />\n                  ) : (\n                    line || ' '\n                  )}\n                </span>\n              </span>\n            ))}\n          </code>\n        </pre>\n      </div>\n    </div>\n  )\n})",
      "type": "registry:component",
      "target": "components/infsh/code-block/code-block.tsx"
    },
    {
      "path": "registry/blocks/code-block/index.ts",
      "content": "// Main components\nexport {\n  CodeBlock,\n  CompactCodeBlock,\n  type CodeBlockProps,\n  type CompactCodeBlockProps,\n} from '@/components/infsh/code-block/code-block'\n\n// Types\nexport type { Token, TokenType, LanguageDefinition } from '@/components/infsh/code-block/types'\n\n// Language utilities\nexport { getLanguage, normalizeLanguage, languages } from '@/components/infsh/code-block/languages'\n\n// Tokenizer (for advanced usage)\nexport { tokenize } from '@/components/infsh/code-block/tokenizer'\n\n// Styles (for customization)\nexport { tokenStyles, getTokenStyle } from '@/components/infsh/code-block/styles'\n",
      "type": "registry:lib",
      "target": "components/infsh/code-block/index.ts"
    },
    {
      "path": "registry/blocks/code-block/lib/types.ts",
      "content": "export type TokenType =\n  | 'comment'\n  | 'string'\n  | 'keyword-import'      // import, export, from, as\n  | 'keyword-declaration' // const, let, var, function, class, type, interface\n  | 'keyword-control'     // if, else, return, for, while\n  | 'keyword-value'       // true, false, null, undefined\n  | 'keyword-other'       // new, this, typeof, etc.\n  | 'number'\n  | 'function'\n  | 'property'\n  | 'operator'\n  | 'punctuation'\n  | 'type'                // type annotations in TS\n  | 'tag'                 // JSX/HTML tag names\n  | 'attribute'           // JSX/HTML attributes\n\nexport interface Token {\n  type: TokenType | null\n  content: string\n}\n\nexport interface LanguageDefinition {\n  name: string\n  aliases: string[]\n  keywords: {\n    import?: Set<string>\n    declaration?: Set<string>\n    control?: Set<string>\n    value?: Set<string>\n    other?: Set<string>\n    type?: Set<string>\n  }\n  patterns: {\n    comment?: RegExp[]\n    string?: RegExp[]\n  }\n}\n",
      "type": "registry:lib",
      "target": "components/infsh/code-block/types.ts"
    },
    {
      "path": "registry/blocks/code-block/lib/languages.ts",
      "content": "import type { LanguageDefinition } from '@/components/infsh/code-block/types'\n\n/**\n * JavaScript/TypeScript/JSX/TSX language definition\n * These all share the same tokenization rules\n */\nexport const javascript: LanguageDefinition = {\n  name: 'javascript',\n  aliases: ['js', 'jsx', 'typescript', 'ts', 'tsx'],\n  keywords: {\n    import: new Set(['import', 'export', 'from', 'as', 'default']),\n    declaration: new Set([\n      'const', 'let', 'var', 'function', 'class', 'extends',\n      'static', 'get', 'set', 'async',\n      // TypeScript-specific\n      'type', 'interface', 'enum', 'namespace', 'module',\n      'declare', 'abstract', 'implements', 'readonly', 'private',\n      'protected', 'public', 'override',\n    ]),\n    control: new Set([\n      'if', 'else', 'for', 'while', 'do', 'switch', 'case',\n      'break', 'continue', 'return', 'try', 'catch', 'finally',\n      'throw', 'await', 'yield', 'of', 'in',\n    ]),\n    value: new Set(['true', 'false', 'null', 'undefined']),\n    other: new Set([\n      'new', 'delete', 'typeof', 'instanceof', 'void', 'this',\n      'super', 'satisfies', 'keyof', 'infer',\n    ]),\n    type: new Set([\n      'string', 'number', 'boolean', 'object', 'symbol', 'bigint',\n      'any', 'unknown', 'never', 'void',\n    ]),\n  },\n  patterns: {\n    comment: [\n      /^\\/\\/[^\\n]*/,           // single line\n      /^\\/\\*[\\s\\S]*?\\*\\//,     // multi line\n    ],\n    string: [\n      /^\"(?:[^\"\\\\]|\\\\.)*\"/,    // double quotes\n      /^'(?:[^'\\\\]|\\\\.)*'/,    // single quotes\n      /^`(?:[^`\\\\]|\\\\.)*`/,    // template literals\n    ],\n  },\n}\n\n/**\n * Python language definition\n */\nexport const python: LanguageDefinition = {\n  name: 'python',\n  aliases: ['py'],\n  keywords: {\n    import: new Set(['import', 'from', 'as']),\n    declaration: new Set(['def', 'class', 'lambda', 'global', 'nonlocal']),\n    control: new Set([\n      'if', 'elif', 'else', 'for', 'while', 'break', 'continue',\n      'return', 'try', 'except', 'finally', 'raise', 'with',\n      'yield', 'pass', 'assert', 'match', 'case',\n    ]),\n    value: new Set(['True', 'False', 'None']),\n    other: new Set(['and', 'or', 'not', 'in', 'is', 'del', 'print', 'self', 'cls']),\n  },\n  patterns: {\n    comment: [\n      /^#[^\\n]*/,\n    ],\n    string: [\n      /^(?:f|r|b|fr|rf|br|rb)?\"\"\"[\\s\\S]*?\"\"\"/,  // triple double\n      /^(?:f|r|b|fr|rf|br|rb)?'''[\\s\\S]*?'''/,  // triple single\n      /^(?:f|r|b)?\"(?:[^\"\\\\]|\\\\.)*\"/,           // double\n      /^(?:f|r|b)?'(?:[^'\\\\]|\\\\.)*'/,           // single\n    ],\n  },\n}\n\n/**\n * Bash/Shell language definition\n */\nexport const bash: LanguageDefinition = {\n  name: 'bash',\n  aliases: ['sh', 'shell', 'zsh'],\n  keywords: {\n    control: new Set([\n      'if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do',\n      'done', 'case', 'esac', 'function', 'return', 'exit',\n      'select', 'until', 'in',\n    ]),\n    other: new Set([\n      'echo', 'export', 'source', 'alias', 'unalias', 'cd', 'pwd',\n      'ls', 'cat', 'grep', 'sed', 'awk', 'curl', 'wget', 'chmod',\n      'chown', 'mkdir', 'rm', 'cp', 'mv', 'touch', 'find', 'xargs',\n      'npm', 'npx', 'yarn', 'pnpm', 'node', 'python', 'pip', 'git',\n      'docker', 'sudo', 'apt', 'brew', 'make', 'cargo', 'go',\n    ]),\n  },\n  patterns: {\n    comment: [\n      /^#[^\\n]*/,\n    ],\n    string: [\n      /^\"(?:[^\"\\\\]|\\\\.)*\"/,\n      /^'(?:[^'\\\\]|\\\\.)*'/,\n    ],\n  },\n}\n\n/**\n * JSON language definition\n */\nexport const json: LanguageDefinition = {\n  name: 'json',\n  aliases: ['jsonc'],\n  keywords: {\n    value: new Set(['true', 'false', 'null']),\n  },\n  patterns: {\n    string: [\n      /^\"(?:[^\"\\\\]|\\\\.)*\"/,\n    ],\n  },\n}\n\n/**\n * CSS language definition\n */\nexport const css: LanguageDefinition = {\n  name: 'css',\n  aliases: ['scss', 'sass', 'less'],\n  keywords: {\n    other: new Set([\n      'important', 'inherit', 'initial', 'unset', 'none', 'auto',\n    ]),\n  },\n  patterns: {\n    comment: [\n      /^\\/\\*[\\s\\S]*?\\*\\//,\n    ],\n    string: [\n      /^\"(?:[^\"\\\\]|\\\\.)*\"/,\n      /^'(?:[^'\\\\]|\\\\.)*'/,\n    ],\n  },\n}\n\n/**\n * HTML/XML language definition\n */\nexport const html: LanguageDefinition = {\n  name: 'html',\n  aliases: ['xml', 'svg', 'htm'],\n  keywords: {},\n  patterns: {\n    comment: [\n      /^<!--[\\s\\S]*?-->/,\n    ],\n    string: [\n      /^\"(?:[^\"\\\\]|\\\\.)*\"/,\n      /^'(?:[^'\\\\]|\\\\.)*'/,\n    ],\n  },\n}\n\n/**\n * Go language definition\n */\nexport const go: LanguageDefinition = {\n  name: 'go',\n  aliases: ['golang'],\n  keywords: {\n    import: new Set(['import', 'package']),\n    declaration: new Set(['func', 'var', 'const', 'type', 'struct', 'interface', 'map', 'chan']),\n    control: new Set([\n      'if', 'else', 'for', 'range', 'switch', 'case', 'default',\n      'break', 'continue', 'return', 'goto', 'fallthrough', 'defer',\n      'go', 'select',\n    ]),\n    value: new Set(['true', 'false', 'nil', 'iota']),\n    other: new Set(['make', 'new', 'len', 'cap', 'append', 'copy', 'delete', 'panic', 'recover']),\n  },\n  patterns: {\n    comment: [\n      /^\\/\\/[^\\n]*/,\n      /^\\/\\*[\\s\\S]*?\\*\\//,\n    ],\n    string: [\n      /^\"(?:[^\"\\\\]|\\\\.)*\"/,\n      /^'(?:[^'\\\\]|\\\\.)*'/,\n      /^`[^`]*`/,  // raw strings\n    ],\n  },\n}\n\n/**\n * Rust language definition\n */\nexport const rust: LanguageDefinition = {\n  name: 'rust',\n  aliases: ['rs'],\n  keywords: {\n    import: new Set(['use', 'mod', 'crate', 'extern', 'as', 'pub']),\n    declaration: new Set([\n      'fn', 'let', 'mut', 'const', 'static', 'struct', 'enum',\n      'trait', 'impl', 'type', 'where',\n    ]),\n    control: new Set([\n      'if', 'else', 'for', 'while', 'loop', 'match', 'break',\n      'continue', 'return', 'async', 'await', 'move',\n    ]),\n    value: new Set(['true', 'false', 'self', 'Self', 'None', 'Some', 'Ok', 'Err']),\n    other: new Set(['unsafe', 'ref', 'dyn', 'macro_rules']),\n  },\n  patterns: {\n    comment: [\n      /^\\/\\/[^\\n]*/,\n      /^\\/\\*[\\s\\S]*?\\*\\//,\n    ],\n    string: [\n      /^\"(?:[^\"\\\\]|\\\\.)*\"/,\n      /^r#*\"[\\s\\S]*?\"#*/,  // raw strings\n      /^'(?:[^'\\\\]|\\\\.)*'/,\n    ],\n  },\n}\n\n/**\n * All supported languages\n */\nexport const languages: Record<string, LanguageDefinition> = {\n  javascript,\n  python,\n  bash,\n  json,\n  css,\n  html,\n  go,\n  rust,\n}\n\n/**\n * Get language definition by name or alias\n */\nexport function getLanguage(name: string): LanguageDefinition | null {\n  const normalized = name.toLowerCase()\n\n  // Direct match\n  if (languages[normalized]) {\n    return languages[normalized]\n  }\n\n  // Check aliases\n  for (const lang of Object.values(languages)) {\n    if (lang.aliases.includes(normalized)) {\n      return lang\n    }\n  }\n\n  return null\n}\n\n/**\n * Normalize language name to canonical form\n */\nexport function normalizeLanguage(language?: string): string {\n  if (!language) return 'text'\n\n  const lang = getLanguage(language)\n  return lang?.name ?? 'text'\n}\n",
      "type": "registry:lib",
      "target": "components/infsh/code-block/languages.ts"
    },
    {
      "path": "registry/blocks/code-block/lib/tokenizer.ts",
      "content": "import type { Token, TokenType, LanguageDefinition } from '@/components/infsh/code-block/types'\nimport { getLanguage } from '@/components/infsh/code-block/languages'\n\nexport interface TokenizeContext {\n  /** Whether we're inside a multiline template literal */\n  inTemplateLiteral?: boolean\n  /** Whether we're inside a multiline comment */\n  inMultilineComment?: boolean\n}\n\n/**\n * Get the keyword type for a word in a given language\n */\nfunction getKeywordType(word: string, lang: LanguageDefinition): TokenType | null {\n  const { keywords } = lang\n\n  if (keywords.import?.has(word)) return 'keyword-import'\n  if (keywords.declaration?.has(word)) return 'keyword-declaration'\n  if (keywords.control?.has(word)) return 'keyword-control'\n  if (keywords.value?.has(word)) return 'keyword-value'\n  if (keywords.type?.has(word)) return 'type'\n  if (keywords.other?.has(word)) return 'keyword-other'\n\n  return null\n}\n\n/**\n * Try to match a pattern at the start of the remaining code\n */\nfunction tryMatch(remaining: string, patterns: RegExp[] | undefined): string | null {\n  if (!patterns) return null\n\n  for (const pattern of patterns) {\n    const match = remaining.match(pattern)\n    if (match) {\n      return match[0]\n    }\n  }\n\n  return null\n}\n\n/**\n * Check if language supports JSX\n */\nfunction isJsxLanguage(languageName: string): boolean {\n  const jsxLangs = ['javascript', 'typescript', 'jsx', 'tsx', 'js', 'ts']\n  return jsxLangs.includes(languageName.toLowerCase())\n}\n\n/**\n * Check if language is JSON\n */\nfunction isJsonLanguage(languageName: string): boolean {\n  return ['json', 'jsonc'].includes(languageName.toLowerCase())\n}\n\n/**\n * Heuristic: detect if a line likely starts inside a JSX tag\n * This handles multi-line JSX where attributes are on separate lines\n *\n * Patterns that suggest we're inside a JSX tag:\n * - Line starts with whitespace + attribute pattern (word followed by = and { or \" or ')\n * - Line starts with whitespace + />\n * - Line starts with whitespace + > followed by content or closing tag\n */\nfunction detectJsxContext(code: string): boolean {\n  // Check for: whitespace + /> (self-closing)\n  if (/^\\s+\\/>/.test(code)) {\n    return true\n  }\n  // Check for: whitespace + > (closing bracket, but not >= operator)\n  if (/^\\s+>(?!=)/.test(code)) {\n    return true\n  }\n  // Check for JSX attribute: whitespace + word + = + { or \" or ' (not word=word which is normal code)\n  // This distinguishes \"  propName={\" from \"  const x =\"\n  if (/^\\s+[a-zA-Z_][a-zA-Z0-9_-]*\\s*=\\s*[{{\"']/.test(code)) {\n    return true\n  }\n  // Check for: whitespace + { (JSX expression in attribute value on its own line)\n  if (/^\\s+\\{\\s*$/.test(code) || /^\\s+\\{[^}]*$/.test(code)) {\n    return true\n  }\n  return false\n}\n\n/**\n * Tokenize a line of code with context for multiline constructs\n */\nexport function tokenize(\n  code: string,\n  languageName: string,\n  context?: TokenizeContext\n): { tokens: Token[]; context: TokenizeContext } {\n  const lang = getLanguage(languageName)\n  const tokens: Token[] = []\n  let remaining = code\n  const supportsJsx = isJsxLanguage(languageName)\n  const isJson = isJsonLanguage(languageName)\n\n  // Carry over context from previous line\n  let inTemplateLiteral = context?.inTemplateLiteral ?? false\n\n  // If we're inside a template literal from a previous line, treat the whole line as a string\n  // until we find a closing backtick\n  if (inTemplateLiteral) {\n    const closingIndex = remaining.indexOf('`')\n    if (closingIndex === -1) {\n      // No closing backtick, entire line is part of string\n      tokens.push({ type: 'string', content: remaining })\n      return { tokens, context: { inTemplateLiteral: true } }\n    } else {\n      // Found closing backtick\n      const stringPart = remaining.slice(0, closingIndex + 1)\n      tokens.push({ type: 'string', content: stringPart })\n      remaining = remaining.slice(closingIndex + 1)\n      inTemplateLiteral = false\n      // Continue tokenizing the rest of the line\n    }\n  }\n\n  // Track if we're inside a JSX tag (between < and >)\n  // Use heuristic to detect if line starts in JSX context\n  let inJsxTag = supportsJsx && detectJsxContext(code) && !inTemplateLiteral\n\n  while (remaining.length > 0) {\n    let matched = false\n\n    // Try to match comments (but not in JSX tag context)\n    if (!inJsxTag && lang?.patterns.comment) {\n      const comment = tryMatch(remaining, lang.patterns.comment)\n      if (comment) {\n        tokens.push({ type: 'comment', content: comment })\n        remaining = remaining.slice(comment.length)\n        matched = true\n        continue\n      }\n    }\n\n    // JSX handling for JS/TS languages\n    if (supportsJsx) {\n      // Opening tag: <Component or <div or </Component\n      const jsxOpenTag = remaining.match(/^<\\/?([A-Z][a-zA-Z0-9]*|[a-z][a-z0-9-]*)/)\n      if (jsxOpenTag) {\n        // Push the < or </\n        const bracket = remaining[0] === '<' && remaining[1] === '/' ? '</' : '<'\n        tokens.push({ type: 'punctuation', content: bracket })\n        remaining = remaining.slice(bracket.length)\n\n        // Push the tag name\n        const tagName = jsxOpenTag[1]\n        tokens.push({ type: 'tag', content: tagName })\n        remaining = remaining.slice(tagName.length)\n\n        inJsxTag = true\n        matched = true\n        continue\n      }\n\n      // Self-closing /> or closing >\n      if (inJsxTag) {\n        if (remaining.startsWith('/>')) {\n          tokens.push({ type: 'punctuation', content: '/>' })\n          remaining = remaining.slice(2)\n          inJsxTag = false\n          matched = true\n          continue\n        }\n        if (remaining.startsWith('>')) {\n          tokens.push({ type: 'punctuation', content: '>' })\n          remaining = remaining.slice(1)\n          inJsxTag = false\n          matched = true\n          continue\n        }\n\n        // JSX attribute: propName= or propName (boolean) or propName (before />)\n        const jsxAttr = remaining.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)(?=\\s*=|\\s*\\/>|\\s*>|\\s+[a-zA-Z_]|\\s*$)/)\n        if (jsxAttr) {\n          tokens.push({ type: 'attribute', content: jsxAttr[1] })\n          remaining = remaining.slice(jsxAttr[1].length)\n          matched = true\n          continue\n        }\n      }\n    }\n\n    // Try to match strings\n    if (lang?.patterns.string) {\n      const str = tryMatch(remaining, lang.patterns.string)\n      if (str) {\n        // In JSON, check if this string is a key (followed by :)\n        const afterStr = remaining.slice(str.length)\n        const isJsonKey = isJson && /^\\s*:/.test(afterStr)\n        tokens.push({ type: isJsonKey ? 'property' : 'string', content: str })\n        remaining = remaining.slice(str.length)\n        matched = true\n        continue\n      }\n\n      // Check for template literal that starts but doesn't end (multiline)\n      // Only trigger if we're AT a backtick and it doesn't close on this line\n      if (remaining[0] === '`') {\n        // Template literal starts but no closing backtick on this line\n        tokens.push({ type: 'string', content: remaining })\n        return { tokens, context: { inTemplateLiteral: true } }\n      }\n    }\n\n    // Fallback string patterns for unknown languages\n    if (!lang) {\n      const doubleStr = remaining.match(/^\"(?:[^\"\\\\]|\\\\.)*\"/)\n      if (doubleStr) {\n        tokens.push({ type: 'string', content: doubleStr[0] })\n        remaining = remaining.slice(doubleStr[0].length)\n        matched = true\n        continue\n      }\n\n      const singleStr = remaining.match(/^'(?:[^'\\\\]|\\\\.)*'/)\n      if (singleStr) {\n        tokens.push({ type: 'string', content: singleStr[0] })\n        remaining = remaining.slice(singleStr[0].length)\n        matched = true\n        continue\n      }\n    }\n\n    // Numbers\n    const number = remaining.match(/^-?\\b\\d+\\.?\\d*(?:e[+-]?\\d+)?\\b/i)\n    if (number) {\n      tokens.push({ type: 'number', content: number[0] })\n      remaining = remaining.slice(number[0].length)\n      matched = true\n      continue\n    }\n\n    // Keywords and identifiers (when not in JSX tag)\n    const word = remaining.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/)\n    if (word) {\n      const afterWord = remaining.slice(word[0].length)\n      // Check if this is an object property key (word followed by : but not ::)\n      const isPropertyKey = /^\\s*:(?!:)/.test(afterWord)\n\n      let tokenType: TokenType | null = null\n      if (isPropertyKey) {\n        tokenType = 'property'\n      } else if (lang && !inJsxTag) {\n        tokenType = getKeywordType(word[0], lang)\n      }\n\n      tokens.push({\n        type: tokenType,\n        content: word[0],\n      })\n      remaining = remaining.slice(word[0].length)\n      matched = true\n      continue\n    }\n\n    // Operators (common across most languages)\n    const operator = remaining.match(\n      /^(?:===|!==|==|!=|<=|>=|=>|->|::|\\.\\.\\.?|\\?\\?|\\?\\.|&&|\\|\\||[+\\-*/%<>=!&|^~?:])/\n    )\n    if (operator) {\n      tokens.push({ type: 'operator', content: operator[0] })\n      remaining = remaining.slice(operator[0].length)\n      matched = true\n      continue\n    }\n\n    // Punctuation\n    const punct = remaining.match(/^[{}[\\]();,.<>]/)\n    if (punct) {\n      tokens.push({ type: 'punctuation', content: punct[0] })\n      remaining = remaining.slice(punct[0].length)\n      matched = true\n      continue\n    }\n\n    // Default: single character (whitespace, etc.)\n    if (!matched) {\n      tokens.push({ type: null, content: remaining[0] })\n      remaining = remaining.slice(1)\n    }\n  }\n\n  return { tokens, context: { inTemplateLiteral } }\n}\n",
      "type": "registry:lib",
      "target": "components/infsh/code-block/tokenizer.ts"
    },
    {
      "path": "registry/blocks/code-block/lib/styles.ts",
      "content": "import type { TokenType } from '@/components/infsh/code-block/types'\n\n/**\n * Tailwind classes for each token type\n * Uses standard Tailwind colors for portability\n */\nexport const tokenStyles: Record<TokenType, string> = {\n  // Comments - muted and italic\n  comment: 'text-zinc-500 italic',\n\n  // Strings - warm amber/orange\n  string: 'text-amber-400',\n\n  // Keywords with semantic grouping\n  'keyword-import': 'text-pink-400 font-medium',         // import, export, from\n  'keyword-declaration': 'text-violet-400 font-medium',  // const, let, function, class\n  'keyword-control': 'text-blue-400 font-medium',        // if, else, return, for\n  'keyword-value': 'text-cyan-400',                      // true, false, null\n  'keyword-other': 'text-violet-400',                    // new, this, typeof\n\n  // Type annotations (TypeScript)\n  type: 'text-cyan-400',\n\n  // Numbers - green\n  number: 'text-emerald-400',\n\n  // Functions - bright green (when detected)\n  function: 'text-green-400',\n\n  // Properties - blue\n  property: 'text-blue-400',\n\n  // Operators and punctuation - subtle\n  operator: 'text-zinc-400',\n  punctuation: 'text-zinc-400',\n\n  // JSX/HTML\n  tag: 'text-red-400',              // <Component, </div>\n  attribute: 'text-orange-300',     // propName=\n}\n\n/**\n * Get the CSS class for a token type\n */\nexport function getTokenStyle(type: TokenType | null): string | null {\n  if (!type) return null\n  return tokenStyles[type] ?? null\n}\n",
      "type": "registry:lib",
      "target": "components/infsh/code-block/styles.ts"
    },
    {
      "path": "registry/blocks/code-block/lib/utils.ts",
      "content": "import React from 'react'\n\n/**\n * Extract text content from React children\n * Handles strings, numbers, arrays, and nested elements\n */\nexport function getTextContent(node: React.ReactNode): string {\n  if (typeof node === 'string') return node\n  if (typeof node === 'number') return String(node)\n  if (!node) return ''\n  if (Array.isArray(node)) return node.map(getTextContent).join('')\n  if (React.isValidElement(node)) {\n    return getTextContent((node.props as { children?: React.ReactNode }).children)\n  }\n  return ''\n}\n\n/**\n * Split code into lines, removing trailing empty line if present\n */\nexport function splitLines(code: string): string[] {\n  const lines = code.split('\\n')\n  if (lines.length > 1 && lines[lines.length - 1] === '') {\n    lines.pop()\n  }\n  return lines\n}\n\n/**\n * Copy text to clipboard\n */\nexport async function copyToClipboard(text: string): Promise<boolean> {\n  try {\n    await navigator.clipboard.writeText(text)\n    return true\n  } catch {\n    return false\n  }\n}\n",
      "type": "registry:lib",
      "target": "components/infsh/code-block/utils.ts"
    }
  ],
  "type": "registry:block"
}