{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stack-trace",
  "type": "registry:component",
  "title": "Stack trace",
  "description": "Parses JavaScript error frames into function names, file locations and internal-frame labels.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/button.json",
    "https://vlak.dev/r/collapsible.json",
    "https://vlak.dev/r/snippet.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/stack-trace.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { Button } from \"./button\";\nimport { Collapsible } from \"./collapsible\";\nimport { SnippetCopy } from \"./snippet\";\n\nexport interface StackFrame { raw: string; functionName?: string; filePath?: string; lineNumber?: number; columnNumber?: number; isInternal: boolean }\nexport interface ParsedStackTrace { errorType?: string; errorMessage: string; frames: StackFrame[]; omittedFrames: number; raw: string }\nexport interface StackTraceProps extends Omit<React.HTMLAttributes<HTMLElement>, \"onCopy\"> {\n  trace: string;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  onFilePathClick?: (filePath: string, line?: number, column?: number) => void;\n  onCopy?: (trace: string) => void | Promise<void>;\n  maxFrames?: number;\n}\n\n/** Parses V8/Node and Firefox/Safari locations; unknown frame text remains readable. */\nexport function parseStackTrace(trace: string, maxFrames = 200): ParsedStackTrace {\n  const limit = Number.isFinite(maxFrames) ? Math.min(2000, Math.max(1, Math.floor(maxFrames))) : 200;\n  const frames: StackFrame[] = [];\n  const messages: string[] = [];\n  let omittedFrames = 0;\n  for (const source of trace.split(\"\\n\")) {\n    const raw = source.trim();\n    if (!raw) continue;\n    const v8 = raw.match(/^at\\s+(?:(.*?)\\s+\\((.*)\\)|(.*))$/);\n    const firefox = !v8 ? raw.match(/^(.*?)@(.+)$/) : null;\n    if (!v8 && !firefox && frames.length === 0) { messages.push(raw); continue; }\n    if (frames.length >= limit) { omittedFrames++; continue; }\n    const location = v8 ? v8[2] ?? v8[3] : firefox?.[2];\n    const full = location?.match(/^(.*):(\\d+):(\\d+)$/);\n    const short = !full ? location?.match(/^(.*):(\\d+)$/) : null;\n    const match = full ?? short;\n    const filePath = match?.[1];\n    frames.push({ raw, functionName: v8?.[1] || firefox?.[1] || undefined, filePath, lineNumber: match ? Number(match[2]) : undefined, columnNumber: full ? Number(full[3]) : undefined, isInternal: /(?:^node:|[/\\\\]node_modules[/\\\\]|(?:^|[/\\\\])internal[/\\\\])/.test(filePath ?? raw) });\n  }\n  const message = messages.join(\"\\n\");\n  const error = message.match(/^([\\w.]*Error|Error|[\\w.]*Exception):\\s*([\\s\\S]*)$/);\n  return { errorType: error?.[1], errorMessage: error?.[2] ?? message, frames, omittedFrames, raw: trace };\n}\nconst styles = stylex.create({\n  root: { minWidth: 0, width: \"100%\", borderWidth: vlak.hairline, borderStyle: \"solid\", borderColor: { default: vlak.divider, [mq.forcedColors]: \"CanvasText\" }, borderRadius: vlak.radiusSm, color: vlak.ink, backgroundColor: vlak.paper, fontSize: \"0.875rem\", lineHeight: 1.45 },\n  header: { display: \"flex\", gap: \"0.75rem\", alignItems: \"flex-start\", padding: \"0.75rem 1rem\" },\n  heading: { minWidth: 0, flex: \"1 1 auto\", display: \"grid\", gap: \"0.25rem\" },\n  title: { fontWeight: 600 },\n  message: { margin: 0, whiteSpace: \"pre-wrap\", overflowWrap: \"anywhere\" },\n  content: { padding: \"0 1rem 1rem\", minWidth: 0 },\n  list: { listStyle: \"none\", margin: 0, padding: 0, display: \"grid\", gap: \"0.5rem\" },\n  frame: { display: \"grid\", gap: \"0.25rem\", minWidth: 0, paddingBlock: \"0.25rem\", color: vlak.ink, fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\", overflowWrap: \"anywhere\" },\n  internal: { color: vlak.gray },\n  location: { minWidth: 0, width: \"fit-content\", maxWidth: \"100%\", textAlign: \"start\", justifyContent: \"flex-start\", overflowWrap: \"anywhere\" },\n  note: { margin: 0, color: vlak.gray, fontSize: \"0.75rem\" },\n});\n\nexport const StackTrace = React.forwardRef<HTMLElement, StackTraceProps>(function StackTrace({ trace, open, defaultOpen = false, onOpenChange, onFilePathClick, onCopy, maxFrames, children, className, style, ...props }, ref) {\n  const parsed = React.useMemo(() => parseStackTrace(trace, maxFrames), [trace, maxFrames]);\n  const root = rs([\"rs-stack-trace\", className], styles.root);\n  const header = rs([\"rs-stack-trace-header\"], styles.header);\n  const heading = rs([\"rs-stack-trace-heading\"], styles.heading);\n  const title = rs([\"rs-stack-trace-title\"], styles.title);\n  const message = rs([\"rs-stack-trace-message\"], styles.message);\n  const content = rs([\"rs-stack-trace-content\"], styles.content);\n  const list = rs([\"rs-stack-trace-list\"], styles.list);\n  const locationStyle = rs([\"rs-stack-trace-location\"], styles.location);\n  const note = rs([\"rs-stack-trace-note\"], styles.note);\n  return <article aria-label={parsed.errorType ?? \"Stack trace\"} {...props} ref={ref} className={root.className} style={{ ...root.style, ...style }}>\n    <header {...header}><div {...heading}><span {...title}>{parsed.errorType ?? \"Stack trace\"}</span>{parsed.errorMessage && <p {...message}>{parsed.errorMessage}</p>}</div><SnippetCopy value={trace} label=\"Copy stack trace\" onCopy={onCopy} /></header>\n    <div {...content}><Collapsible title={`${parsed.frames.length} stack ${parsed.frames.length === 1 ? \"frame\" : \"frames\"}`} open={open} defaultOpen={defaultOpen} onToggle={event => { const next = event.currentTarget.open; if (open !== undefined && next !== open) event.currentTarget.open = open; if (next !== open) onOpenChange?.(next); }}>\n      <ol {...list}>{parsed.frames.map((frame, index) => {\n        const frameStyle = rs([\"rs-stack-trace-frame\", frame.isInternal && \"rs-stack-trace-internal\"], styles.frame, frame.isInternal && styles.internal);\n        const location = frame.filePath ? `${frame.filePath}:${frame.lineNumber}${frame.columnNumber != null ? `:${frame.columnNumber}` : \"\"}` : frame.raw;\n        return <li key={`${index}:${frame.raw}`} {...frameStyle}>{frame.functionName && <span>{frame.functionName}</span>}{frame.filePath && onFilePathClick ? <Button {...locationStyle} type=\"button\" variant=\"subtle\" onClick={() => onFilePathClick(frame.filePath!, frame.lineNumber, frame.columnNumber)}>{location}</Button> : <span>{location}</span>}{frame.isInternal && <span {...note}>Internal frame</span>}</li>;\n      })}</ol>{parsed.omittedFrames > 0 && <p {...note}>{parsed.omittedFrames} additional frames omitted. Copy includes the full trace.</p>}{parsed.frames.length === 0 && <p {...note}>No stack frames supplied.</p>}\n    </Collapsible>{children}</div>\n  </article>;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/stack-trace.tsx"
    },
    {
      "path": "vlak/styles/stack-trace.css",
      "content": "/* ── stack-trace: generated from packages/react/src/components/stack-trace.tsx ── */\n.rs-stack-trace{min-width:0;width:100%;border-width:1px;border-style:solid;border-color:var(--divider);border-radius:var(--radius-sm);color:var(--text);background-color:var(--bg);font-size:0.875rem;line-height:1.45}\n@media (forced-colors: active){.rs-stack-trace{border-color:CanvasText}}\n.rs-stack-trace-header{display:flex;gap:0.75rem;align-items:flex-start;padding:0.75rem 1rem}\n.rs-stack-trace-heading{min-width:0;flex:1 1 auto;display:grid;gap:0.25rem}\n.rs-stack-trace-title{font-weight:600}\n.rs-stack-trace-message{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}\n.rs-stack-trace-content{padding:0 1rem 1rem;min-width:0}\n.rs-stack-trace-list{list-style:none;margin:0;padding:0;display:grid;gap:0.5rem}\n.rs-stack-trace-frame{display:grid;gap:0.25rem;min-width:0;padding-block:0.25rem;color:var(--text);font-family:ui-monospace, SFMono-Regular, Menlo, monospace;overflow-wrap:anywhere}\n.rs-stack-trace-internal{color:var(--text-secondary)}\n.rs-stack-trace-location{min-width:0;width:fit-content;max-width:100%;text-align:start;justify-content:flex-start;overflow-wrap:anywhere}\n.rs-stack-trace-note{margin:0;color:var(--text-secondary);font-size:0.75rem}\n",
      "type": "registry:file",
      "target": "styles/vlak/stack-trace.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "ai",
      "classes": [
        "rs-stack-trace",
        "rs-stack-trace-header",
        "rs-stack-trace-heading",
        "rs-stack-trace-title",
        "rs-stack-trace-message",
        "rs-stack-trace-content",
        "rs-stack-trace-list",
        "rs-stack-trace-location",
        "rs-stack-trace-note",
        "rs-stack-trace-frame",
        "rs-stack-trace-internal"
      ],
      "snippet": "<article class=\"rs-stack-trace\" aria-label=\"TypeError\"><header class=\"rs-stack-trace-header\"><div class=\"rs-stack-trace-heading\"><span class=\"rs-stack-trace-title\">TypeError</span><p class=\"rs-stack-trace-message\">Draft must contain text</p></div></header><div class=\"rs-stack-trace-content\"><details class=\"rs-disclosure\" open><summary class=\"rs-disclosure-summary\">1 stack frame</summary><ol class=\"rs-stack-trace-list\"><li class=\"rs-stack-trace-frame\"><span>sendPrompt</span><span>src/composer.tsx:42:5</span></li></ol></details></div></article>",
      "cssOnly": false,
      "registryDependencies": [
        "button",
        "collapsible",
        "snippet",
        "vlak-lib"
      ],
      "aliases": [
        "AI Elements Stack Trace",
        "JavaScript error",
        "Error frames"
      ],
      "example": "import { StackTrace } from \"@noorddev/vlak-react\";\n\n<StackTrace trace={\"TypeError: Draft must contain text\\n  at sendPrompt (src/composer.tsx:42:5)\\n  at node:internal/process/task_queues:95:5\"} defaultOpen />",
      "usage": {
        "use": [
          "Inspect supplied V8, Node, Firefox or Safari stack frames, preserving Windows drive names and URL paths.",
          "Use onFilePathClick to navigate to a file, line and column; unknown frame text remains readable.",
          "Use open/onOpenChange for controlled disclosure and maxFrames to bound displayed frames. Copy retains the full raw trace."
        ],
        "avoid": [
          "Executing a file path or inferring its contents from an error frame.",
          "Assuming every runtime stack syntax can be parsed; unsupported frame text is retained."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Tab reaches raw-trace copy, the frame disclosure and optional location buttons."
        },
        {
          "keys": "Enter, Space",
          "does": "Enter or Space activates copy, toggles the frame summary or requests navigation to a location."
        }
      ],
      "a11y": [
        "Error type and message are visible text; internal frames have a readable label rather than opacity alone.",
        "Native disclosure semantics expose expanded state.",
        "All file callbacks are explicit user actions; parsing never opens files."
      ]
    }
  }
}
