VibeCoderzVibeCoderz
Telegram
All Prompts
CodingAgentTraceViewer UI Preview

CodingAgentTraceViewer

Визуализация трассировок сессий кодинг-агентов: мысли, выполнение инструментов, сетевые вызовы, токены. Дашборд для отладки.

by Emil VaaglandLive Preview

Prompt

# CodingAgentTraceViewer

You are given a task to integrate an existing React component in the codebase

~~~/README.md
# Coding Agent Trace Viewer

A specialized observability dashboard for visualizing coding agent sessions. This component parses and displays trace logs in a high-density, developer-friendly timeline.

## Features

- **Timeline Visualization**: Linear view of agent activities (thoughts, tool calls, messages).
- **Hierarchical Tool Execution**: See nested tool results and errors.
- **Deep Inspection**: Click any event to see full JSON payloads and metadata.
- **Filtering & Search**: Quickly find specific events or filter by type.
- **Metrics Dashboard**: Real-time summary of tokens, cost, and duration.
- **High-Contrast Dark Theme**: Designed for long debugging sessions.

## Usage

```tsx
import { CodingAgentTraceViewer } from '@/sd-components/d470ceb9-954b-4f9d-9a67-6abb1574238c';

function DebugPage() {
  return (
    <div className="h-screen w-full">
      <CodingAgentTraceViewer />
    </div>
  );
}
```

## Props

Currently, the component uses an internal mock data generator (`generateMockData`). In a real implementation, you would pass a `events` prop.

| Prop | Type | Description |
|------|------|-------------|
| `events` | `TraceEvent[]` | (Planned) Array of trace events to visualize |
| `theme` | `'light' | 'dark'` | (Optional) Force a specific theme |

## Event Types supported

- `thought`: Internal reasoning logs
- `tool_call`: External tool execution attempts
- `tool_result`: Returns from tools
- `user_message`: Input from the user
- `assistant_message`: Final response to user
- `system_event`: Errors, warnings, or system logs
~~~

~~~/src/App.tsx
import React from 'react';
import { CodingAgentTraceViewer } from './Component';

export default function App() {
  return (
    <div className="w-full h-screen">
      <CodingAgentTraceViewer />
    </div>
  );
}
~~~

~~~/package.json
{
  "name": "coding-agent-trace-viewer",
  "description": "Observability dashboard for coding agent sessions",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "lucide-react": "^0.344.0",
    "framer-motion": "^11.0.8",
    "date-fns": "^3.3.1",
    "clsx": "^2.1.0",
    "tailwind-merge": "^2.2.1"
  }
}
~~~

~~~/src/Component.tsx
import React, { useState, useMemo, useRef, useEffect } from 'react';
import { 
  Terminal, 
  MessageSquare, 
  Cpu, 
  Clock, 
  Check, 
  AlertCircle,
  Zap,
  ChevronRight, 
  ChevronDown, 
  Play,
  FileText,
  User,
  Hash,
  Copy,
  DollarSign
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { format } from 'date-fns';
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

// --- Utility ---
function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

// --- Types ---
type EventType = 'thought' | 'skill_call' | 'bash_command' | 'user_message' | 'assistant_message' | 'system_event' | 'parallel_group';
type Status = 'success' | 'error' | 'pending' | 'warning' | 'running';

interface TraceEvent {
  id: string;
  turnId?: number;
  type: EventType;
  timestamp: string;
  title?: string;
  content?: string | any;
  status?: Status;
  duration?: number; // in seconds
  tokens?: number;
  agentId?: string;
  children?: TraceEvent[];
  meta?: {
    path?: string;
    repo?: string;
    url?: string;
    command?: string;
  };
}

interface SessionMetrics {
  totalDuration: number;
  totalTokens: number;
  totalCost: number;
  toolCount: number;
  agentCount: number;
  fileCount: number;
}

interface TokenUsage {
  type: string;
  tokens: number;
  cost: number | string;
}

interface ToolUsage {
  name: string;
  count: number;
}

// --- Mock Data Generator ---
const generateMockData = (): TraceEvent[] => {
  const now = Date.now();
  
  return [
    // TURN 1
    {
      id: 'evt_1',
      turnId: 1,
      type: 'user_message',
      timestamp: new Date(now - 1000 * 60 * 21).toISOString(),
      content: 'Find the 2 latest critical severity codeql findings in the finn org on github.schibsted.io and triage them',
      agentId: '84a4f03e-a08',
      status: 'success'
    },
    {
      id: 'evt_2',
      turnId: 1,
      type: 'skill_call',
      timestamp: new Date(now - 1000 * 60 * 20.9).toISOString(),
      title: 'codeql-analysis',
      agentId: '22ad5408-94f4',
      status: 'success',
      content: 'Launching skill: codeql-analysis',
      children: []
    },
    {
      id: 'evt_3',
      turnId: 1,
      type: 'thought',
      timestamp: new Date(now - 1000 * 60 * 20.8).toISOString(),
      content: 'The user wants me to find the 2 latest critical severity CodeQL findings in the finn org on github.schibsted.io and triage them. Let me think about what skills might apply here: 1. `codeql-analysis` - This skill fetches CodeQL alerts and code context 2. `/triage-codeql` command - For triaging CodeQL security alerts I should first use the codeql-analysis skill to find the critical severity findings, then triage them. Let me start by invoking the codeql-analysis skill to understand how to fetch the alerts....',
      status: 'success'
    },
    {
      id: 'evt_4',
      turnId: 1,
      type: 'skill_call',
      timestamp: new Date(now - 1000 * 60 * 20.5).toISOString(),
      title: 'codeql-analysis',
      agentId: '3c1fbc49-027e',
      status: 'success',
      content: 'Base directory for this skill: /Users/emil/source/nmp-sec/agent-lab/operatives/.claude/skills/codeql-analysis',
      children: []
    },
    
    // TURN 2 (Implied by context, though grouped under same timeline visuals usually)
    {
      id: 'evt_5',
      turnId: 2,
      type: 'parallel_group',
      timestamp: new Date(now - 1000 * 60 * 18).toISOString(),
      title: 'PARALLEL EXECUTION (2 AGENTS)',
      status: 'running',
      duration: 292.7, // seconds
      tokens: 292000,
      content: [
        { name: 'Triage SSRF alert #117', agent: 'CODEQL-TRIAGE', status: 'running', id: 'a10f4a0' },
        { name: 'Triage XSS alert #104', agent: 'CODEQL-TRIAGE', status: 'success', id: 'b21g5b1' }
      ],
      children: [
        {
          id: 'evt_5_1',
          turnId: 2,
          type: 'bash_command',
          timestamp: new Date(now - 1000 * 60 * 18).toISOString(),
          title: 'Fetch CodeQL alert details',
          content: 'cd /Users/emil/source/nmp-sec/agent-lab/operatives/.claude/skills/codeql-analysis && python -m src.cli alert "https://github.schibsted.io/finn/apps-adinput/security/code-scanning/117"',
          status: 'success',
          duration: 4.3,
          meta: { command: 'bash' },
          children: [
            {
               id: 'evt_5_1_out',
               turnId: 2,
               type: 'system_event', // Output
               timestamp: new Date(now - 1000 * 60 * 17.9).toISOString(),
               content: `## java/ssrf\n\n**Repository**: finn/apps-adinput\n**Alert**: #117\n**Severity**: critical\n**CWE**: CWE-918\n\n**Location**:\n\`src/main/java/no/finntech/apps/adinput/api/services/item/ItemManagementHttpClient.java:95\`\n**Message**: Potential server-side request forgery due to a user-provided value.\nPotential server-side request forgery due to a user-provided value.\n\n**URL**: https://github.schibsted.io/finn/apps-adinput/security/code-scanning/117\n[CODEQL-03E369]`
            }
          ]
        }
      ]
    },
    
    // TURN 3
    {
      id: 'evt_6',
      turnId: 3,
      type: 'bash_command',
      timestamp: new Date(now - 1000 * 60 * 15).toISOString(),
      title: 'Check logs for user 999999 lookup',
      content: `humioctl search --no-progress aurora_view 'nmp_app = "ua-core-api" | @rawstring = *999999*' -s 2m 2>&1`,
      status: 'success',
      meta: { command: 'bash' },
      children: [
        {
           id: 'evt_6_out',
           turnId: 3,
           type: 'system_event',
           timestamp: new Date(now - 1000 * 60 * 14.9).toISOString(),
           content: `2026-01-10T19:13:08.938+01:00 Not found: Login id not found: 999999`
        }
      ]
    },
    {
      id: 'evt_7',
      turnId: 3,
      type: 'thought',
      timestamp: new Date(now - 1000 * 60 * 14.8).toISOString(),
      content: 'Let me check the logs to see what happened....',
      status: 'success'
    },
    {
      id: 'evt_8',
      turnId: 3,
      type: 'assistant_message',
      timestamp: new Date(now - 1000 * 60 * 14).toISOString(),
      title: 'CLAUDE',
      agentId: '2a2bc230-f98',
      content: `The path traversal is confirmed working. The internal ua-core-api service attempted to look up user ID 999999, proving the SSRF/path traversal vulnerability is exploitable. Let me summarize the PoC:\n**Confirmed SSRF via Path Traversal** **PoC URL:** \`\`\` https://www.finn.no/ua-front/creative-preview?classified_id=../../user/schibsted-account/999999 \`\`\` **Evidence from Humio logs:** \`\`\` 2026-01-10T19:13:08.938+01:00 Not found: Login id not found: 999999 \`\`\` This proves: 1. The user-controlled...`,
      status: 'success'
    }
  ];
};

// --- Sub-Components for Timeline View ---

const TurnSeparator = ({ turnId, deltaTokens }: { turnId: number, deltaTokens?: number }) => (
  // 1. IMPROVEMENT: "Make sticky to help orientation in long traces"
  <div className="sticky top-0 z-30 flex items-center gap-4 py-4 select-none bg-[#09090b]/95 backdrop-blur supports-[backdrop-filter]:bg-[#09090b]/80 border-b border-transparent transition-all duration-200">
    <div className="bg-[#1e1e24] px-3 py-1 rounded text-xs font-bold text-blue-300 uppercase tracking-wider shadow-sm ring-1 ring-white/5">
      Turn {turnId}
    </div>
    <div className="h-px bg-white/10 flex-1" />
    {deltaTokens && (
      <div className="bg-red-500/10 border border-red-500/20 text-red-400 px-2 py-0.5 rounded text-[10px] font-mono">
        +{deltaTokens.toLocaleString()} tok
      </div>
    )}
  </div>
);

const UserMessageCard = ({ event }: { event: TraceEvent }) => (
  <div className="mb-4 animate-in fade-in slide-in-from-bottom-2 duration-500">
    <div className="flex items-center justify-between mb-2 px-1">
      <div className="flex items-center gap-2">
        <span className="text-[10px] font-mono text-gray-500">{format(new Date(event.timestamp), 'HH:mm:ss')}</span>
        <span className="text-xs font-bold text-blue-400 tracking-wide">USER</span>
      </div>
      {event.agentId && <span className="text-[10px] font-mono text-gray-600 bg-white/5 px-1.5 py-0.5 rounded">{event.agentId}</span>}
    </div>
    {/* 2. IMPROVEMENT: "Visual separation is weak. Increase border contrast" */}
    <div className="bg-[#121214] border border-white/20 rounded-lg p-4 text-gray-200 text-sm leading-relaxed shadow-sm">
      {event.content}
    </div>
  </div>
);

const SkillCard = ({ event }: { event: TraceEvent }) => {
  const [isOpen, setIsOpen] = useState(true);
  
  return (
    // 2. IMPROVEMENT: Increased border contrast
    <div className="mb-4 rounded-lg border border-white/20 bg-[#0e0e10] overflow-hidden shadow-sm animate-in fade-in slide-in-from-bottom-2 duration-500">
      <div 
        className="flex items-center justify-between p-3 bg-white/5 cursor-pointer hover:bg-white/10 transition-colors"
        onClick={() => setIsOpen(!isOpen)}
      >
        <div className="flex items-center gap-2">
          <Zap className="w-3.5 h-3.5 text-yellow-400 fill-yellow-400/20" />
          <span className="text-xs font-bold text-gray-200 tracking-wide uppercase">SKILL</span>
          <span className="text-xs font-mono text-gray-400">{event.title}</span>
        </div>
        <div className="flex items-center gap-3">
           {event.agentId && <span className="text-[10px] font-mono text-gray-600 hidden sm:inline-block">{event.agentId}</span>}
           <ChevronDown className={cn("w-4 h-4 text-gray-500 transition-transform", !isOpen && "-rotate-90")} />
        </div>
      </div>
      
      {isOpen && (
        <div className="p-4 border-t border-white/5 text-sm text-gray-400 font-mono bg-[#09090b]">
           {event.content}
        </div>
      )}
    </div>
  );
};

const BashCard = ({ event }: { event: TraceEvent }) => {
  const [isOpen, setIsOpen] = useState(true);
  
  return (
    // 2. IMPROVEMENT: Increased border contrast
    <div className="mb-4 rounded-lg border border-white/20 bg-[#0e0e10] overflow-hidden shadow-sm group animate-in fade-in slide-in-from-bottom-2 duration-500">
      <div className="flex items-center justify-between p-2 pl-3 bg-[#18181b] border-b border-white/5">
        <div className="flex items-center gap-3">
          <div className="flex items-center gap-2">
            <span className="text-[10px] font-mono font-bold text-gray-500 uppercase">
              {format(new Date(event.timestamp), 'HH:mm:ss')}
            </span>
            <div className="px-1.5 py-0.5 rounded bg-purple-500/10 text-purple-400 text-[10px] font-bold uppercase tracking-wider">
              BASH
            </div>
          </div>
          <span className="text-xs text-gray-400 font-medium truncate max-w-[300px]">{event.title || 'Execute command'}</span>
        </div>
        <div className="flex items-center gap-3">
           {event.status === 'success' && <Check className="w-3.5 h-3.5 text-emerald-500" />}
           {event.duration && <span className="text-[10px] font-mono text-gray-600">{event.duration}s</span>}
           {event.agentId && <span className="text-[10px] font-mono text-gray-700 bg-white/5 px-1 rounded">{event.agentId}</span>}
        </div>
      </div>
      
      <div className="p-3 bg-[#09090b] font-mono text-xs overflow-x-auto custom-scrollbar">
         <div className="flex gap-2 text-gray-300">
            <span className="text-emerald-500 select-none">$</span>
            <span className="whitespace-pre-wrap break-all">{event.content}</span>
         </div>
         
         {event.children?.map(child => (
           <div key={child.id} className="mt-3 pt-3 border-t border-white/10 text-gray-400 whitespace-pre-wrap leading-relaxed">
              {child.content}
           </div>
         ))}
      </div>
    </div>
  );
};

// 3. IMPROVEMENT: "Use collapsible 'Reasoning' component"
const ThoughtCard = ({ event }: { event: TraceEvent }) => {
  const [isExpanded, setIsExpanded] = useState(false);
  const preview = typeof event.content === 'string' 
    ? event.content.slice(0, 100) + (event.content.length > 100 ? '...' : '')
    : 'Thinking...';

  return (
    <div className="mb-4 px-2 animate-in fade-in slide-in-from-bottom-2 duration-500">
      <button 
        className="flex items-center gap-2 mb-2 group select-none focus:outline-none"
        onClick={() => setIsExpanded(!isExpanded)}
      >
        <div className="p-0.5 rounded bg-white/5 group-hover:bg-white/10 transition-colors">
          <ChevronRight className={cn("w-3 h-3 text-gray-500 transition-transform duration-200", isExpanded && "rotate-90")} />
        </div>
        <span className="text-xs font-bold text-gray-500 uppercase tracking-wider group-hover:text-gray-400 transition-colors">Reasoning</span>
        {!isExpanded && <span className="text-xs text-gray-600 font-serif italic truncate max-w-[300px]">{preview}</span>}
      </button>
      
      <AnimatePresence>
        {isExpanded && (
          <motion.div
            initial={{ height: 0, opacity: 0 }}
            animate={{ height: 'auto', opacity: 1 }}
            exit={{ height: 0, opacity: 0 }}
            transition={{ duration: 0.2 }}
            className="overflow-hidden"
          >
            <div className="text-gray-400 text-sm leading-relaxed whitespace-pre-wrap font-serif opacity-90 italic pl-6 border-l-2 border-white/10 hover:border-white/20 transition-colors py-1">
              {event.content}
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
};

const AssistantMessageCard = ({ event }: { event: TraceEvent }) => {
  // Format markdown-like content simply
  const contentSegments = (event.content as string).split(/(\*\*.*?\*\*|`{3}[\s\S]*?`{3})/g);

  return (
    <div className="mb-6 animate-in fade-in slide-in-from-bottom-2 duration-500">
       <div className="flex items-center justify-between mb-2 px-1">
        <div className="flex items-center gap-2">
          <span className="text-[10px] font-mono text-gray-500">{format(new Date(event.timestamp), 'HH:mm:ss')}</span>
          <span className="text-xs font-bold text-purple-400 tracking-wide uppercase">{event.title || 'ASSISTANT'}</span>
        </div>
        {event.agentId && <span className="text-[10px] font-mono text-gray-600 bg-white/5 px-1.5 py-0.5 rounded">{event.agentId}</span>}
      </div>
      {/* 2. IMPROVEMENT: Increased border contrast */}
      <div className="bg-[#121214] border border-white/20 rounded-lg p-5 text-gray-300 text-sm shadow-sm">
        {contentSegments.map((segment, i) => {
          if (segment.startsWith('**') && segment.endsWith('**')) {
            return <strong key={i} className="text-white font-bold">{segment.slice(2, -2)}</strong>;
          }
          if (segment.startsWith('```') && segment.endsWith('```')) {
            return (
              <div key={i} className="my-3 bg-black/30 p-3 rounded border border-white/5 font-mono text-xs text-emerald-400 overflow-x-auto">
                {segment.slice(3, -3)}
              </div>
            );
          }
          return <span key={i}>{segment}</span>;
        })}
      </div>
    </div>
  );
};

const ParallelGroupCard = ({ event }: { event: TraceEvent }) => {
  const [isExpanded, setIsExpanded] = useState(true);
  const subAgents = event.content as any[];

  return (
    // 2. IMPROVEMENT: Increased border contrast for parallel group
    <div className="mb-6 rounded-lg border border-purple-500/30 bg-[#121214]/50 overflow-hidden ring-1 ring-purple-500/10 animate-in fade-in slide-in-from-bottom-2 duration-500">
      {/* Header */}
      <div className="flex items-center justify-between p-2 pl-3 bg-[#18181b]/80 border-b border-white/10 backdrop-blur-sm">
        <div 
          className="flex items-center gap-3 cursor-pointer"
          onClick={() => setIsExpanded(!isExpanded)}
        >
          <div className="p-1 rounded bg-purple-500/20">
             <ChevronDown className={cn("w-3 h-3 text-purple-400 transition-transform", !isExpanded && "-rotate-90")} />
          </div>
          <span className="text-xs font-bold text-gray-200">{event.title}</span>
        </div>
        
        <div className="flex items-center gap-4 text-[10px] font-mono text-gray-500">
          <span>{subAgents.length} ops</span>
          <span>{(event.tokens || 0 / 1000).toFixed(1)}K tok</span>
          <span>{format(new Date(event.duration || 0 * 1000), 'm')}m {format(new Date(event.duration || 0 * 1000), 'ss')}s</span>
          
          <div className="flex items-center gap-1.5 pl-2 border-l border-white/10">
             <div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
             <span className="text-emerald-400">{event.id}</span>
             <span className="text-red-400">● {(event.tokens || 0 / 1000).toFixed(1)}K</span>
          </div>
        </div>
      </div>

      {isExpanded && (
        <div className="bg-[#0c0c0e]/50">
           {/* Sub Agents Status Bar */}
           <div className="flex border-b border-white/5">
             {subAgents.map((agent: any, i: number) => (
                <div key={i} className="flex-1 px-4 py-2 border-r border-white/5 last:border-0 hover:bg-white/5 transition-colors cursor-pointer group">
                  <div className="flex items-center justify-between mb-1">
                     <span className="text-[10px] font-bold text-gray-400 group-hover:text-white transition-colors">{agent.name}</span>
                     {agent.status === 'success' ? (
                       <Check className="w-3 h-3 text-emerald-500" />
                     ) : (
                       <div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
                     )}
                  </div>
                  <div className="flex items-center justify-between text-[10px] font-mono text-gray-600">
                     <span>{agent.agent}</span>
                     <span>{agent.id}</span>
                  </div>
                </div>
             ))}
           </div>
           
           {/* Child Events Container */}
           <div className="p-4 bg-[#09090b]/30">
              {event.children?.map(child => (
                <div key={child.id}>
                  {child.type === 'bash_command' && <BashCard event={child} />}
                  {child.type === 'thought' && <ThoughtCard event={child} />}
                  {child.type === 'skill_call' && <SkillCard event={child} />}
                </div>
              ))}
           </div>
        </div>
      )}
    </div>
  );
};


// --- New Components for Summary View ---

const SummaryView = () => {
  const tokenUsage: TokenUsage[] = [
    { type: 'Input', tokens: 3566, cost: 0.01 },
    { type: 'Output', tokens: 43124, cost: 0.65 },
    { type: 'Cache read', tokens: 17559648, cost: 'free' },
    { type: 'Cache write', tokens: 2277337, cost: 8.54 },
  ];

  const tools: ToolUsage[] = [
    { name: 'Read', count: 45 },
    { name: 'TodoWrite', count: 15 },
    { name: 'Write', count: 14 },
    { name: 'Edit', count: 9 },
    { name: 'Bash', count: 7 },
    { name: 'Glob', count: 7 },
    { name: 'Skill', count: 1 },
    { name: 'Task', count: 1 },
    { name: 'Grep', count: 1 },
  ];

  return (
    <div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
      {/* Session Header */}
      <div>
        <div className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-2">SESSION</div>
        <h2 className="text-xl font-bold text-gray-100 mb-2">15m 23s session with 1 sub-agents</h2>
        <div className="text-sm text-gray-400">
          <span className="text-gray-200">19,883,675 tokens</span> · ~$39.91 estimated · <span className="text-gray-500">Vertex AI · Opus</span>
        </div>
        <div className="text-sm text-gray-500 mt-1">
          Agents: 1,272,015 tokens
        </div>
      </div>

      {/* Token Usage Table */}
      <div className="rounded-lg border border-white/5 bg-[#121214] overflow-hidden">
        <div className="divide-y divide-white/5">
          {tokenUsage.map((item, idx) => (
            <div key={idx} className="flex items-center justify-between px-4 py-3 text-sm">
              <span className="text-gray-400 font-medium">{item.type}</span>
              <div className="flex items-center gap-12">
                <span className="text-gray-300 font-mono">{item.tokens.toLocaleString()} tokens</span>
                <span className={cn(
                  "font-mono w-16 text-right",
                  item.cost === 'free' ? "text-emerald-500" : "text-gray-300"
                )}>
                  {typeof item.cost === 'number' ? `$${item.cost.toFixed(2)}` : item.cost}
                </span>
              </div>
            </div>
          ))}
        </div>
        <div className="bg-white/[0.02] px-4 py-2 text-[11px] text-gray-500 italic border-t border-white/5">
          Cost is approximate. Actual billing may differ based on provider pricing and caching.
        </div>
      </div>

      {/* Tools Section */}
      <div>
        <div className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-3">TOOLS</div>
        <h3 className="text-lg font-bold text-gray-200 mb-2">100 tool calls</h3>
        <div className="flex flex-wrap gap-x-3 gap-y-2 text-sm font-mono text-gray-400">
          {tools.map((tool, idx) => (
            <div key={idx} className="flex items-center gap-1.5">
              <span className="text-gray-300">{tool.name}</span>
              <span className="text-gray-600">{tool.count}</span>
              {idx < tools.length - 1 && <span className="text-gray-700 mx-1">·</span>}
            </div>
          ))}
        </div>
      </div>

      {/* Sub-Agents Section */}
      <div>
        <div className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-3">SUB-AGENTS</div>
        <h3 className="text-lg font-bold text-gray-200 mb-4">1 delegated tasks</h3>
        
        <div className="rounded-lg border border-white/5 bg-[#121214] p-4 flex items-center justify-between hover:bg-white/[0.07] transition-colors cursor-pointer group">
          <span className="text-sm font-medium text-gray-300 group-hover:text-white transition-colors">Review observability plugin</span>
          <span className="text-xs font-mono text-gray-500 bg-white/5 px-2 py-1 rounded">superpowers:code-reviewer</span>
        </div>
      </div>

       {/* Files Modified Section */}
       <div>
        <div className="text-xs font-bold text-gray-500 uppercase tracking-widest mb-3">FILES MODIFIED</div>
        <div className="rounded-lg border border-white/5 bg-[#121214] p-4">
           <div className="text-sm text-gray-500 italic text-center py-4">
              15 files modified (165 edits)
           </div>
        </div>
      </div>
    </div>
  );
};

// --- New Components for Events (Table) View ---

const EventsView = ({ events }: { events: TraceEvent[] }) => {
  const [searchQuery, setSearchQuery] = useState('');
  const [typeFilter, setTypeFilter] = useState<string>('all');
  
  const flattenedEvents = useMemo(() => {
    const flat: TraceEvent[] = [];
    const traverse = (evts: TraceEvent[], depth = 0) => {
      evts.forEach(e => {
        flat.push({ ...e, _depth: depth } as any);
        if (e.children) traverse(e.children, depth + 1);
      });
    };
    traverse(events);
    return flat;
  }, [events]);

  const filteredEvents = useMemo(() => {
    return flattenedEvents.filter(e => {
      const matchesSearch = 
        (e.content && typeof e.content === 'string' && e.content.toLowerCase().includes(searchQuery.toLowerCase())) ||
        (e.title && e.title.toLowerCase().includes(searchQuery.toLowerCase())) ||
        (e.type && e.type.toLowerCase().includes(searchQuery.toLowerCase())) ||
        (e.id && e.id.toLowerCase().includes(searchQuery.toLowerCase()));
      
      const matchesType = typeFilter === 'all' || e.type === typeFilter;
      
      return matchesSearch && matchesType;
    });
  }, [flattenedEvents, searchQuery, typeFilter]);

  const eventTypes = ['all', ...Array.from(new Set(flattenedEvents.map(e => e.type)))];

  // Map types to icons
  const getTypeIcon = (type: string) => {
    switch(type) {
      case 'user_message': return <User className="w-4 h-4" />;
      case 'assistant_message': return <MessageSquare className="w-4 h-4" />;
      case 'bash_command': return <Terminal className="w-4 h-4" />;
      case 'skill_call': return <Zap className="w-4 h-4" />;
      case 'thought': return <Cpu className="w-4 h-4" />;
      case 'parallel_group': return <Copy className="w-4 h-4" />;
      default: return <FileText className="w-4 h-4" />;
    }
  };

  return (
    <div className="flex h-full animate-in fade-in slide-in-from-bottom-4 duration-500">
       {/* Left Sidebar Filter */}
       <div className="w-64 flex-none border-r border-white/5 bg-[#0c0c0e] p-4 flex flex-col gap-6">
          
          {/* Search */}
          <div>
            <div className="text-xs font-bold text-gray-500 uppercase tracking-wider mb-3">Search Events</div>
            <div className="relative">
               <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                  <Hash className="h-3.5 w-3.5 text-gray-500" />
               </div>
               <input
                  type="text"
                  placeholder="Search..."
                  className="w-full bg-[#18181b] text-gray-200 text-sm rounded-md border border-white/10 pl-9 pr-3 py-2 focus:outline-none focus:border-purple-500/50 focus:ring-1 focus:ring-purple-500/50 placeholder:text-gray-600 transition-all"
                  value={searchQuery}
                  onChange={(e) => setSearchQuery(e.target.value)}
               />
            </div>
          </div>

          {/* Type Filters */}
          <div className="flex-1 overflow-y-auto">
            <div className="text-xs font-bold text-gray-500 uppercase tracking-wider mb-3">Event Types</div>
            <div className="space-y-1">
              {eventTypes.map(type => (
                <button
                  key={type}
                  onClick={() => setTypeFilter(type)}
                  className={cn(
                    "w-full flex items-center justify-between px-3 py-2 rounded-md text-sm transition-all group",
                    typeFilter === type 
                      ? "bg-purple-500/10 text-purple-400 border border-purple-500/20" 
                      : "text-gray-400 hover:bg-white/5 hover:text-gray-200 border border-transparent"
                  )}
                >
                  <div className="flex items-center gap-2.5">
                    {type === 'all' ? <Hash className="w-4 h-4 opacity-70" /> : getTypeIcon(type)}
                    <span className="capitalize">{type.replace('_', ' ')}</span>
                  </div>
                  {typeFilter === type && <Check className="w-3.5 h-3.5" />}
                  {type !== 'all' && typeFilter !== type && (
                    <span className="text-[10px] text-gray-600 bg-white/5 px-1.5 py-0.5 rounded group-hover:bg-white/10">
                      {flattenedEvents.filter(e => e.type === type).length}
                    </span>
                  )}
                </button>
              ))}
            </div>
          </div>
          
          {/* Stats Summary */}
          <div className="pt-4 border-t border-white/5">
             <div className="bg-[#121214] rounded-lg p-3 border border-white/5">
                <div className="text-xs text-gray-500 mb-1">Showing</div>
                <div className="text-xl font-bold text-gray-200">
                  {filteredEvents.length} <span className="text-sm font-normal text-gray-500">/ {flattenedEvents.length} events</span>
                </div>
             </div>
          </div>
       </div>

       {/* Main Data Table Area */}
       <div className="flex-1 overflow-hidden flex flex-col bg-[#09090b]">
          <div className="flex-1 overflow-y-auto custom-scrollbar p-6">
            <div className="rounded-lg border border-white/5 bg-[#121214] overflow-hidden shadow-sm">
                <div className="overflow-x-auto">
                  <table className="w-full text-left border-collapse">
                      <thead>
                        <tr className="bg-[#18181b] text-xs font-bold text-gray-500 uppercase tracking-wider border-b border-white/5">
                            <th className="px-4 py-3 w-[120px]">Time</th>
                            <th className="px-4 py-3 w-[140px]">Type</th>
                            <th className="px-4 py-3">Content / Summary</th>
                            <th className="px-4 py-3 w-[100px] text-right">Dur</th>
                            <th className="px-4 py-3 w-[80px] text-center">Status</th>
                        </tr>
                      </thead>
                      <tbody className="divide-y divide-white/5 text-sm font-mono">
                        {filteredEvents.map((event) => (
                            <tr 
                              key={event.id} 
                              className="hover:bg-white/[0.03] transition-colors group cursor-default"
                            >
                              <td className="px-4 py-2.5 text-gray-500 whitespace-nowrap text-xs">
                                  {format(new Date(event.timestamp), 'HH:mm:ss.SSS')}
                              </td>
                              <td className="px-4 py-2.5">
                                  <div className="flex items-center gap-2">
                                    <div className={cn(
                                        "p-1 rounded bg-opacity-10",
                                        event.type === 'user_message' && "bg-blue-500 text-blue-400",
                                        event.type === 'assistant_message' && "bg-purple-500 text-purple-400",
                                        event.type === 'bash_command' && "bg-pink-500 text-pink-400",
                                        event.type === 'skill_call' && "bg-yellow-500 text-yellow-400",
                                        event.type === 'thought' && "bg-gray-500 text-gray-400",
                                        event.type === 'system_event' && "bg-gray-700 text-gray-400",
                                        event.type === 'parallel_group' && "bg-emerald-500 text-emerald-400",
                                    )}>
                                      {getTypeIcon(event.type)}
                                    </div>
                                    <span className="text-xs text-gray-400 capitalize truncate w-24">
                                      {event.type.replace('_', ' ')}
                                    </span>
                                  </div>
                              </td>
                              <td className="px-4 py-2.5 max-w-lg">
                                  <div className="truncate text-gray-300 flex items-center gap-2">
                                    {(event as any)._depth > 0 && (
                                        <span className="text-gray-700 flex-none" style={{ width: (event as any)._depth * 16 }}>
                                          └
                                        </span>
                                    )}
                                    <span className="truncate">
                                        {event.title || (typeof event.content === 'string' ? event.content : JSON.stringify(event.content))}
                                    </span>
                                  </div>
                              </td>
                              <td className="px-4 py-2.5 text-right text-gray-500 text-xs">
                                  {event.duration ? `${event.duration}s` : '-'}
                              </td>
                              <td className="px-4 py-2.5 text-center">
                                  {event.status === 'success' && <Check className="w-3.5 h-3.5 text-emerald-500 mx-auto" />}
                                  {event.status === 'running' && <div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse mx-auto" />}
                                  {!event.status && <span className="text-gray-700">-</span>}
                              </td>
                            </tr>
                        ))}
                      </tbody>
                  </table>
                </div>
            </div>
          </div>
       </div>
    </div>
  );
};

// --- Main Component ---

const RawView = ({ events }: { events: TraceEvent[] }) => {
  return (
    <div className="space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-500">
      <div className="flex items-center justify-between mb-6">
         <div className="text-xs font-bold text-gray-500 uppercase tracking-widest">RAW EVENT LOG</div>
      </div>

      {events.map((event, idx) => (
         <RawEventCard key={event.id} event={event} index={idx} />
      ))}
    </div>
  );
};

const RawEventCard = ({ event, index }: { event: TraceEvent, index: number }) => {
   const [isOpen, setIsOpen] = useState(false);

   return (
      <div className="rounded-lg border border-white/5 bg-[#121214] overflow-hidden">
         <div 
            className="flex items-center justify-between p-3 cursor-pointer hover:bg-white/[0.03] transition-colors"
            onClick={() => setIsOpen(!isOpen)}
         >
            <div className="flex items-center gap-3">
               <span className="text-[10px] font-mono text-gray-600 uppercase w-16 text-right">raw_{index}</span>
               <span className="text-xs font-bold text-emerald-500 uppercase tracking-wider">{event.type}</span>
               <span className="text-xs text-gray-500 truncate max-w-[400px]">{event.id}</span>
            </div>
            <ChevronDown className={cn("w-4 h-4 text-gray-600 transition-transform", isOpen && "rotate-180")} />
         </div>

         {isOpen && (
            <div className="p-4 border-t border-white/5 bg-[#0e0e10]">
               <div className="flex items-center gap-2 mb-3 text-xs text-gray-500 uppercase tracking-wider">
                  <ChevronRight className="w-3 h-3" />
                  Show raw event
               </div>
               <pre className="text-[11px] font-mono text-gray-400 leading-relaxed overflow-x-auto whitespace-pre-wrap">
                  {JSON.stringify(event, null, 2)}
               </pre>
            </div>
         )}
      </div>
   );
};

// --- Main Component ---

export function CodingAgentTraceViewer() {
  const [events] = useState<TraceEvent[]>(generateMockData());
  const [activeTab, setActiveTab] = useState<'timeline' | 'summary' | 'raw' | 'events'>('timeline');

  // Metrics calculation
  const metrics: SessionMetrics = {
    totalDuration: 1295, // 21m 35s
    totalTokens: 42300,
    totalCost: 4.258499,
    toolCount: 72,
    agentCount: 2,
    fileCount: 1
  };

  // Group events by turn
  const eventsByTurn = useMemo(() => {
    const groups: Record<number, TraceEvent[]> = {};
    events.forEach(e => {
      if (e.turnId) {
        if (!groups[e.turnId]) groups[e.turnId] = [];
        groups[e.turnId].push(e);
      }
    });
    return groups;
  }, [events]);

  const renderContent = () => {
     switch(activeTab) {
        case 'timeline':
           return (
              <div className="max-w-4xl mx-auto px-4 py-8 pb-24">
                 {Object.entries(eventsByTurn).map(([turnId, turnEvents]) => (
                   <div key={turnId}>
                     <TurnSeparator turnId={Number(turnId)} deltaTokens={turnId === '2' ? 5881 : undefined} />
                     {turnEvents.map(event => {
                        switch(event.type) {
                          case 'user_message': return <UserMessageCard key={event.id} event={event} />;
                          case 'bash_command': return <BashCard key={event.id} event={event} />;
                          case 'skill_call': return <SkillCard key={event.id} event={event} />;
                          case 'assistant_message': return <AssistantMessageCard key={event.id} event={event} />;
                          case 'thought': return <ThoughtCard key={event.id} event={event} />;
                          case 'parallel_group': return <ParallelGroupCard key={event.id} event={event} />;
                          default: return null;
                        }
                     })}
                   </div>
                 ))}
              </div>
           );
        case 'summary':
           return (
              <div className="max-w-3xl mx-auto px-4 py-8 pb-24">
                 <SummaryView />
              </div>
           );
        case 'raw':
           return (
              <div className="max-w-4xl mx-auto px-4 py-8 pb-24">
                 <RawView events={events} />
              </div>
           );
        case 'events':
           return <EventsView events={events} />;
        default: return null;
     }
  };

  return (
    <div className="flex flex-col h-screen w-full bg-[#09090b] text-gray-200 overflow-hidden font-sans selection:bg-purple-500/30">
      
      {/* Top Header */}
      <div className="flex-none bg-[#0c0c0e] border-b border-white/5 pt-4 px-6 pb-0 z-20">
        <div className="flex items-center justify-between mb-4">
           <div>
             <h1 className="text-xl font-bold text-white mb-1">82 Tool Calls - Plugin Marketplace</h1>
             <div className="flex items-center gap-2 text-xs font-mono text-gray-500">
               {/* 1. IMPROVEMENT: "Low Contrast - Boost legibility" */}
               <span className="text-gray-400">bf3b6bad</span>
               <span className="text-gray-600">•</span>
               <span className="text-gray-400">/Users/emil/source/nmp-sec/claude-plugin-marketplace</span>
               <span className="text-gray-600">•</span>
               <span className="text-gray-400">add-safety-hooks-plugin</span>
             </div>
           </div>
        </div>
        
        {/* Tabs */}
        <div className="flex items-center gap-1">
          {['timeline', 'events', 'summary', 'raw'].map(tab => (
            <button
              key={tab}
              onClick={() => setActiveTab(tab as any)}
              className={cn(
                "px-4 py-2 text-sm font-medium border-b-2 transition-colors",
                activeTab === tab 
                  ? "text-white border-white" 
                  : "text-gray-500 border-transparent hover:text-gray-300 hover:border-gray-700"
              )}
            >
              {tab.charAt(0).toUpperCase() + tab.slice(1)}
            </button>
          ))}
        </div>
      </div>

      {/* Main Content Area */}
      <div className={cn(
        "flex-1 relative bg-[#09090b]",
        activeTab === 'events' ? "overflow-hidden" : "overflow-y-auto custom-scrollbar"
      )}>
        {renderContent()}
      </div>

      {/* Bottom Status Bar */}
      {/* 4. IMPROVEMENT: "Data overload. Group metrics logically" */}
      <div className="flex-none bg-[#0c0c0e] border-t border-white/5 py-2 px-4 flex items-center justify-between z-20 shadow-[0_-10px_40px_rgba(0,0,0,0.5)]">
        
        <div className="flex items-center gap-6 text-[11px] font-mono text-gray-500 w-full">
           {/* Group 1: Context */}
           <div className="flex items-center gap-3 pr-6 border-r border-white/5">
              <span className="text-xs font-bold text-gray-400 tracking-wider">CONTEXT</span>
              <div className="flex items-center gap-2 w-[180px]">
                <div className="w-full h-1.5 bg-[#27272a] rounded-full overflow-hidden">
                  <div className="h-full bg-emerald-500 w-[21%]" />
                </div>
                <span className="text-emerald-500 font-medium">47%</span>
              </div>
           </div>

           {/* Group 2: Session */}
           <div className="flex items-center gap-4 pr-6 border-r border-white/5">
              <span className="text-xs font-bold text-gray-400 tracking-wider">SESSION</span>
              <div className="flex items-center gap-1.5">
                 <Clock className="w-3 h-3 text-gray-400" />
                 <span className="text-gray-300">15m 23s</span>
              </div>
              
              <div className="flex items-center gap-1.5">
                 <DollarSign className="w-3 h-3 text-yellow-600" />
                 <span className="text-gray-300">$39.91</span>
              </div>
           </div>

           {/* Group 3: Activity */}
           <div className="flex items-center gap-4">
              <span className="text-xs font-bold text-gray-400 tracking-wider">ACTIVITY</span>
              <div className="flex items-center gap-1.5">
                 <Terminal className="w-3 h-3 text-purple-400" />
                 <span className="text-gray-300">100 tools</span>
              </div>
              
              <div className="flex items-center gap-1.5">
                 <Cpu className="w-3 h-3 text-blue-400" />
                 <span className="text-gray-300">1 agents</span>
              </div>
              
              <div className="flex items-center gap-1.5">
                 <FileText className="w-3 h-3 text-gray-400" />
                 <span className="text-gray-300">15 files</span>
              </div>
           </div>
        </div>
      </div>
    </div>
  );
}

export default CodingAgentTraceViewer;
~~~

Implementation Guidelines

1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system

Help me integrate this into my design
All Prompts