# Stack Trace

A failed tool call with its stack frames, linking your own frames and folding the framework ones.

Source: https://suiss.app/ui/docs/ai/stack-trace

**React**

**Example**

```tsx
import { FileCodeIcon, StackIcon, TerminalIcon } from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const FRAME = /^at (.+) \((.+):(\d+):(\d+)\)$/

const STACK = `TypeError: Cannot read properties of undefined (reading 'email')
    at seedUsers (prisma/seed.ts:27:34)
    at main (prisma/seed.ts:51:9)
    at Module._compile (node:internal/modules/cjs/loader:1554:14)
    at Object.transformer (node_modules/tsx/dist/register.cjs:3:1104)
    at Module.load (node:internal/modules/cjs/loader:1289:32)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)`

const RESET_OUTPUT = `Database reset successful

The following migrations have been applied:

migrations/
  └─ 20260912_init/
  └─ 20260920_add_profiles/`

function parseFrame(line: string) {
  const match = line.match(FRAME)

  return {
    line,
    name: match?.[1],
    file: match?.[2],
    lineNumber: match?.[3],
    internal: line.includes("node_modules") || line.includes("node:"),
  }
}

const [error, ...lines] = STACK.split("\n").map((line) => line.trim())
const frames = lines.map(parseFrame)
const appFrames = frames.filter((frame) => !frame.internal)
const internalFrames = frames.filter((frame) => frame.internal)

export default function StackTraceDemo() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4 py-6 text-sm">
      <Message align="end">
        <MessageContent>
          <Bubble align="end" variant="muted">
            <BubbleContent>Reset the database and seed it.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent className="gap-4">
          <Trace>
            <TraceCollapsible>
              <TraceCollapsibleTrigger>
                <TraceLine>
                  <TraceIcon>
                    <TerminalIcon />
                  </TraceIcon>
                  <TraceTitle>
                    Ran pnpm prisma migrate reset --skip-seed
                  </TraceTitle>
                </TraceLine>
              </TraceCollapsibleTrigger>
              <TraceCollapsibleContent>
                <CodeBlock size="sm">
                  <CodeBlockContent wrap className="max-h-64">
                    {RESET_OUTPUT}
                  </CodeBlockContent>
                </CodeBlock>
              </TraceCollapsibleContent>
            </TraceCollapsible>
            <TraceCollapsible defaultOpen>
              <TraceCollapsibleTrigger>
                <TraceLine status="error">
                  <TraceIcon>
                    <TerminalIcon />
                  </TraceIcon>
                  <TraceTitle>Failed: {error}</TraceTitle>
                </TraceLine>
              </TraceCollapsibleTrigger>
              <TraceCollapsibleContent className="flex flex-col gap-2">
                <p className="font-mono text-xs text-destructive">{error}</p>
                <Trace>
                  {appFrames.map((frame) => (
                    <a
                      key={frame.line}
                      href={`#${frame.file}:${frame.lineNumber}`}
                    >
                      <TraceLine>
                        <TraceIcon>
                          <FileCodeIcon />
                        </TraceIcon>
                        <TraceTitle>
                          {frame.name} in {frame.file}:{frame.lineNumber}
                        </TraceTitle>
                      </TraceLine>
                    </a>
                  ))}
                  <TraceCollapsible>
                    <TraceCollapsibleTrigger>
                      <TraceLine>
                        <TraceIcon>
                          <StackIcon />
                        </TraceIcon>
                        <TraceTitle>
                          {internalFrames.length} framework frames
                        </TraceTitle>
                      </TraceLine>
                    </TraceCollapsibleTrigger>
                    <TraceCollapsibleContent>
                      <ol className="flex flex-col gap-1 overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground">
                        {internalFrames.map((frame) => (
                          <li key={frame.line} className="whitespace-nowrap">
                            {frame.line}
                          </li>
                        ))}
                      </ol>
                    </TraceCollapsibleContent>
                  </TraceCollapsible>
                </Trace>
              </TraceCollapsibleContent>
            </TraceCollapsible>
          </Trace>
          <p>
            The migrations ran, but the seed failed in seedUsers. One fixture
            has no profile, so profile.email is undefined.
          </p>
        </MessageContent>
      </Message>
    </div>
  )
}
```

Stack Trace is a row for a tool call that failed. The error is the title, such as "Failed: TypeError". Open it for the stack frames, which can link to the file and line.

The frames are a `TraceCollapsible` with a nested `Trace`. For a failure inside a test run, use [Test Results](https://suiss.app/ui/docs/ai/test-results).

## Installation

Stack Trace is built with the [Trace](https://suiss.app/ui/docs/components/trace) and [Code Block](https://suiss.app/ui/docs/components/code-block) components.

```bash title="Terminal"
npx shadcn@latest add @esuiss/trace @esuiss/code-block
```

Works in any project with a `components.json`. See [Installation](https://suiss.app/ui/docs/installation#add-components) for the one-line registry setup. Or use the [suiss/ui CLI](https://suiss.app/ui/docs/cli):

```bash title="Terminal"
npx @esuiss/ui@latest add trace code-block
```

## Usage

```tsx
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"
```

```tsx
const [error, ...frames] = stack.split("\n").map((line) => line.trim())

<Trace>
  <TraceCollapsible>
    <TraceCollapsibleTrigger>
      <TraceLine status="error">
        <TraceIcon>
          <CircleXIcon />
        </TraceIcon>
        <TraceTitle>Failed: {error}</TraceTitle>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <div className="flex flex-col gap-2 rounded-md bg-muted p-3 font-mono text-xs">
        <p className="text-destructive">{error}</p>
        <ol>
          {frames.map((frame) => (
            <li
              key={frame}
              className={frame.includes("node_modules") ? undefined : "text-foreground"}
            >
              {frame}
            </li>
          ))}
        </ol>
      </div>
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>
```

The title truncates, so repeat the full error at the top of the content. Frames from `node_modules` stay muted, so your own frames stand out.

## Examples

### Frame links

Turns your own frames into links that open the file at the line, and folds the framework frames into a collapsible row of their own, so the frames you can fix come first.

**Example**

```tsx
import { FileCodeIcon, StackIcon, XCircleIcon } from "@phosphor-icons/react"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const FRAME = /^at (.+) \((.+):(\d+):(\d+)\)$/

const STACK = `TypeError: Cannot read properties of undefined (reading 'total')
    at formatInvoice (app/lib/billing.ts:42:31)
    at InvoiceRow (app/components/invoice-row.tsx:18:12)
    at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:14985:18)
    at beginWork (node_modules/react-dom/cjs/react-dom.development.js:19049:16)
    at performUnitOfWork (node_modules/react-dom/cjs/react-dom.development.js:22776:12)
    at workLoopSync (node_modules/react-dom/cjs/react-dom.development.js:22707:5)`

function parseFrame(line: string) {
  const match = line.match(FRAME)

  return {
    line,
    name: match?.[1],
    file: match?.[2],
    lineNumber: match?.[3],
    internal: line.includes("node_modules"),
  }
}

const [error, ...lines] = STACK.split("\n").map((line) => line.trim())
const frames = lines.map(parseFrame)
const appFrames = frames.filter((frame) => !frame.internal)
const internalFrames = frames.filter((frame) => frame.internal)

export default function StackTraceFrames() {
  return (
    <Trace className="max-w-sm">
      <TraceCollapsible defaultOpen>
        <TraceCollapsibleTrigger>
          <TraceLine status="error">
            <TraceIcon>
              <XCircleIcon />
            </TraceIcon>
            <TraceTitle>Failed: {error}</TraceTitle>
          </TraceLine>
        </TraceCollapsibleTrigger>
        <TraceCollapsibleContent keepMounted className="flex flex-col gap-2">
          <p className="font-mono text-xs text-destructive">{error}</p>
          <Trace>
            {appFrames.map((frame) => (
              <a key={frame.line} href={`#${frame.file}:${frame.lineNumber}`}>
                <TraceLine>
                  <TraceIcon>
                    <FileCodeIcon />
                  </TraceIcon>
                  <TraceTitle>
                    {frame.name} in {frame.file}:{frame.lineNumber}
                  </TraceTitle>
                </TraceLine>
              </a>
            ))}
            <TraceCollapsible>
              <TraceCollapsibleTrigger>
                <TraceLine>
                  <TraceIcon>
                    <StackIcon />
                  </TraceIcon>
                  <TraceTitle>
                    {internalFrames.length} framework frames
                  </TraceTitle>
                </TraceLine>
              </TraceCollapsibleTrigger>
              <TraceCollapsibleContent>
                <ol className="flex flex-col gap-1 overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground">
                  {internalFrames.map((frame) => (
                    <li key={frame.line} className="whitespace-nowrap">
                      {frame.line}
                    </li>
                  ))}
                </ol>
              </TraceCollapsibleContent>
            </TraceCollapsible>
          </Trace>
        </TraceCollapsibleContent>
      </TraceCollapsible>
    </Trace>
  )
}
```

## AI SDK

When a tool throws, its part is `output-error`. By default `errorText` is only the error message, with no frames. Return `error.stack` from `onError` in `toUIMessageStreamResponse` to send them.

```ts title="app/api/chat/route.ts"
return result.toUIMessageStreamResponse({
  onError: (error) =>
    error instanceof Error ? (error.stack ?? error.message) : String(error),
})
```

A stack shows file paths from your server, so only send it in development.

```tsx
if (part.state === "output-error") {
  const [error, ...frames] = part.errorText
    .split("\n")
    .map((line) => line.trim())

  return <TraceCollapsible>…</TraceCollapsible>
}
```

Map the other tool states as in [Tool](https://suiss.app/ui/docs/ai/tool).

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace) and [Code Block](https://suiss.app/ui/docs/components/code-block).

**React Native, SwiftUI, Jetpack Compose**

```tsx
import { WarningCircleIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { Message, MessageContent } from "@/components/ui/message"
import { Text } from "@/components/ui/text"
import { TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function AiStackTraceExample() {
  return (
    <View className="w-full gap-4">
      <Message align="end">
        <MessageContent>
          <Bubble variant="muted">
            <BubbleContent>Reset the database and seed it.</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <TraceCollapsible defaultOpen>
        <TraceCollapsibleTrigger>
          <TraceLine status="error">
            <TraceIcon>
              <Icon as={WarningCircleIcon} />
            </TraceIcon>
            <TraceTitle>Failed to run pnpm db:seed</TraceTitle>
          </TraceLine>
        </TraceCollapsibleTrigger>
        <TraceCollapsibleContent>
          <CodeBlock size="sm" variant="destructive">
            <CodeBlockContent wrap>
              {
                "TypeError: Cannot read properties of undefined (reading 'id')\n  at seedOrders (scripts/seed.ts:42:18)\n  at main (scripts/seed.ts:12:3)"
              }
            </CodeBlockContent>
          </CodeBlock>
        </TraceCollapsibleContent>
      </TraceCollapsible>
      <Text>
        The seed reads a customer before it is created. Seeding customers first
        fixes the order step.
      </Text>
    </View>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct StackTraceExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 16) {
      Message(align: .end) {
        MessageContent {
          Bubble(variant: .muted, align: .end) { BubbleContent("Reset the database and seed it.") }
        }
      }
      TraceCollapsible(initiallyExpanded: true) {
        TraceCollapsibleTrigger {
          TraceLine(status: .error) {
            TraceIcon { Ph.warningCircle.regular }
            TraceTitle(verbatim: "Failed to run pnpm db:seed")
          }
        }
        TraceCollapsibleContent {
          CodeBlock(.destructive, size: .sm) { CodeBlockContent("TypeError: Cannot read properties of undefined (reading 'id')\\n  at seedOrders (scripts/seed.ts:42:18)\\n  at main (scripts/seed.ts:12:3)", wrap: true) }
        }
      }
      Text("The seed reads a customer before it is created. Seeding customers first fixes the order step.").textStyle(.base).foregroundStyle(EsuissColor.foreground)
    }
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.WarningCircle
import com.esuiss.ui.components.Bubble
import com.esuiss.ui.components.BubbleContent
import com.esuiss.ui.components.BubbleVariant
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockSize
import com.esuiss.ui.components.CodeBlockVariant
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Message
import com.esuiss.ui.components.MessageAlign
import com.esuiss.ui.components.MessageContent
import com.esuiss.ui.components.Text
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceStatus
import com.esuiss.ui.components.TraceTitle

@Composable
fun StackTraceAIDefault() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Message(Modifier.fillMaxWidth(), align = MessageAlign.End) {
            MessageContent {
                Bubble(variant = BubbleVariant.Muted) { BubbleContent("Reset the database and seed it.") }
            }
        }
        TraceCollapsible(initiallyExpanded = true) {
            TraceCollapsibleTrigger {
                TraceLine(status = TraceStatus.Error) {
                    TraceIcon { Icon(PhosphorIcons.Regular.WarningCircle, contentDescription = null) }
                    TraceTitle("Failed to run pnpm db:seed")
                }
            }
            TraceCollapsibleContent {
                CodeBlock(variant = CodeBlockVariant.Destructive, size = CodeBlockSize.Sm) { CodeBlockContent("TypeError: Cannot read properties of undefined (reading 'id')\n  at seedOrders (scripts/seed.ts:42:18)\n  at main (scripts/seed.ts:12:3)", wrap = true) }
            }
        }
        Text("The seed reads a customer before it is created. Seeding customers first fixes the order step.")
    }
}
```

Stack Trace on mobile is built from the same parts as on the web: [Trace](https://suiss.app/ui/docs/components/trace), [Code Block](https://suiss.app/ui/docs/components/code-block), [Message](https://suiss.app/ui/docs/components/message), [Bubble](https://suiss.app/ui/docs/components/bubble). The example above is a static demo; connect it to your agent by rendering the same parts from your messages.

## Installation

**React Native**

```bash title="Terminal"
npx @esuiss/ui@latest add trace code-block message bubble
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add trace code-block message bubble --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add trace code-block message bubble --platform kotlin
```

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace), [Code Block](https://suiss.app/ui/docs/components/code-block), [Message](https://suiss.app/ui/docs/components/message), [Bubble](https://suiss.app/ui/docs/components/bubble).

