diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/SpanTree.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/SpanTree.tsx
new file mode 100644
index 00000000000..78a3497125d
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/SpanTree.tsx
@@ -0,0 +1,203 @@
+"use client";
+
+import { useEffect, useRef } from "react";
+
+import { cn } from "@/lib/cva.config";
+
+import { SPAN_BAR_CLASS, SpanTypePill } from "./TracePills";
+import type { Span } from "./traceTypes";
+import { fmtCost, fmtMs, fmtTok, GROUP_PAGE_SIZE, spanLabel, type SpanGroup, type TreeRow } from "./traceUtils";
+
+interface SpanTreeProps {
+ rows: TreeRow[];
+ traceDurationMs: number;
+ selectedId: string | null;
+ onSelect: (spanId: string) => void;
+ onToggleCollapse: (spanId: string) => void;
+ /** Reveal `shown` invocations of a collapsed agent group (0 hides them again). */
+ onGroupShow: (groupKey: string, shown: number) => void;
+}
+
+const MIN_BAR_PERCENT = 0.3;
+
+const spanMeta = (span: Span): string => {
+ if (span.litellm) {
+ return `${fmtTok(span.litellm.prompt_tokens)}→${fmtTok(span.litellm.completion_tokens)} · ${fmtCost(span.litellm.spend)}`;
+ }
+ return fmtMs(span.duration_ms);
+};
+
+export const groupSummary = (group: SpanGroup): string => {
+ const errors = group.errors ? ` · ${group.errors} error${group.errors === 1 ? "" : "s"}` : "";
+ return `${group.name} ×${group.spans.length} · ${fmtCost(group.spend)} · p50 ${fmtMs(group.p50Ms)}${errors}`;
+};
+
+function Guides({ depth }: { depth: number }) {
+ return (
+ <>
+ {Array.from({ length: depth }, (_, i) => (
+
+ ))}
+ >
+ );
+}
+
+function WaterfallBar({ span, total }: { span: Span; total: number }) {
+ const left = total > 0 ? (span.start_offset_ms / total) * 100 : 0;
+ const width = total > 0 ? Math.max((span.duration_ms / total) * 100, MIN_BAR_PERCENT) : MIN_BAR_PERCENT;
+ return (
+
+
+
+ );
+}
+
+interface SpanRowProps {
+ row: Extract;
+ total: number;
+ selected: boolean;
+ onSelect: (spanId: string) => void;
+ onToggleCollapse: (spanId: string) => void;
+}
+
+function SpanRow({ row, total, selected, onSelect, onToggleCollapse }: SpanRowProps) {
+ const { span } = row;
+ const handleToggle = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ onToggleCollapse(span.span_id);
+ };
+ return (
+ onSelect(span.span_id)}
+ className={cn(
+ "grid min-h-7 cursor-pointer grid-cols-[minmax(0,1fr)_28%] items-center gap-2.5 border-l-2 border-transparent px-3 py-0.5 hover:bg-muted/60",
+ selected && "border-l-primary bg-primary/5",
+ )}
+ >
+
+
+
+
+ {spanLabel(span)}
+ {spanMeta(span)}
+
+
+
+ );
+}
+
+function GroupRow({
+ row,
+ onGroupShow,
+}: {
+ row: Extract;
+ onGroupShow: SpanTreeProps["onGroupShow"];
+}) {
+ const open = row.shown > 0;
+ return (
+ onGroupShow(row.group.key, open ? 0 : GROUP_PAGE_SIZE)}
+ className="flex min-h-7 cursor-pointer items-center gap-1.5 px-3 py-0.5 hover:bg-muted/60"
+ >
+
+ {open ? "▾" : "▸"}
+
+ 0 && "text-destructive")}>
+ {groupSummary(row.group)}
+
+
+ );
+}
+
+function MoreRow({
+ row,
+ onGroupShow,
+}: {
+ row: Extract;
+ onGroupShow: SpanTreeProps["onGroupShow"];
+}) {
+ const remaining = row.group.spans.length - row.shown;
+ return (
+
+
+
+
+ );
+}
+
+const rowKey = (row: TreeRow): string => (row.kind === "span" ? row.span.span_id : `${row.kind}:${row.group.key}`);
+
+/** Waterfall tree of visible spans. Rows are computed by `flattenTree` in the drawer. */
+export function SpanTree({
+ rows,
+ traceDurationMs,
+ selectedId,
+ onSelect,
+ onToggleCollapse,
+ onGroupShow,
+}: SpanTreeProps) {
+ const scrollRef = useRef(null);
+
+ useEffect(() => {
+ if (!selectedId) return;
+ const node = scrollRef.current?.querySelector(`[data-span-id="${selectedId}"]`);
+ if (node && "scrollIntoView" in node) (node as HTMLElement).scrollIntoView({ block: "nearest" });
+ }, [selectedId, rows]);
+
+ return (
+
+
+ Span
+
+ 0
+ {fmtMs(traceDurationMs / 2)}
+ {fmtMs(traceDurationMs)}
+
+
+
+ {rows.map((row) => {
+ if (row.kind === "group") return ;
+ if (row.kind === "more") return ;
+ return (
+
+ );
+ })}
+
+
+ );
+}