@mtharrison/loupe 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20995,6 +20995,82 @@ var init_theme = __esm({
20995
20995
  function deriveSessionNavItems(sessionNodes, traceById) {
20996
20996
  return sessionNodes.map((node) => deriveSessionNavItem(node, traceById)).sort(compareSessionNavItems);
20997
20997
  }
20998
+ function sortSessionNodesForNav(sessionNodes, traceById) {
20999
+ const itemById = new Map(
21000
+ sessionNodes.map((node) => [node.id, deriveSessionNavItem(node, traceById)])
21001
+ );
21002
+ return sessionNodes.slice().sort(
21003
+ (left, right) => compareSessionNavItems(
21004
+ itemById.get(left.id),
21005
+ itemById.get(right.id)
21006
+ )
21007
+ );
21008
+ }
21009
+ function findSessionNodePath(nodes, id, trail = []) {
21010
+ for (const node of nodes) {
21011
+ const nextTrail = [...trail, node];
21012
+ if (node.id === id) {
21013
+ return nextTrail;
21014
+ }
21015
+ const childTrail = findSessionNodePath(node.children, id, nextTrail);
21016
+ if (childTrail.length) {
21017
+ return childTrail;
21018
+ }
21019
+ }
21020
+ return [];
21021
+ }
21022
+ function findSessionNodeById(nodes, id) {
21023
+ return findSessionNodePath(nodes, id).at(-1) ?? null;
21024
+ }
21025
+ function getNewestTraceIdForNode(node) {
21026
+ if (!node?.traceIds.length) {
21027
+ return null;
21028
+ }
21029
+ if (typeof node.meta?.traceId === "string" && node.meta.traceId) {
21030
+ return node.meta.traceId;
21031
+ }
21032
+ return node.traceIds[0] || null;
21033
+ }
21034
+ function resolveSessionTreeSelection(sessionNodes, selectedNodeId, selectedTraceId) {
21035
+ const selectedNode = selectedNodeId ? findSessionNodeById(sessionNodes, selectedNodeId) : null;
21036
+ const selectedTraceNode = selectedTraceId ? findSessionNodeById(sessionNodes, `trace:${selectedTraceId}`) : null;
21037
+ const fallbackNode = selectedNode ?? selectedTraceNode ?? sessionNodes[0] ?? null;
21038
+ if (!fallbackNode) {
21039
+ return {
21040
+ selectedNodeId: null,
21041
+ selectedTraceId: null
21042
+ };
21043
+ }
21044
+ const nextSelectedNodeId = selectedNode?.id ?? fallbackNode.id;
21045
+ const nextSelectedTraceId = selectedTraceId && fallbackNode.traceIds.includes(selectedTraceId) ? selectedTraceId : getNewestTraceIdForNode(fallbackNode);
21046
+ return {
21047
+ selectedNodeId: nextSelectedNodeId,
21048
+ selectedTraceId: nextSelectedTraceId
21049
+ };
21050
+ }
21051
+ function getDefaultExpandedSessionTreeNodeIds(sessionNodes, activeSessionId, selectedNodeId) {
21052
+ const expanded = /* @__PURE__ */ new Set();
21053
+ const activeSession = (activeSessionId ? sessionNodes.find((node) => node.id === activeSessionId) ?? null : null) ?? sessionNodes[0] ?? null;
21054
+ if (!activeSession) {
21055
+ return expanded;
21056
+ }
21057
+ if (activeSession.children.length) {
21058
+ expanded.add(activeSession.id);
21059
+ }
21060
+ visitSessionTree(activeSession.children, (node) => {
21061
+ if (node.children.length && node.type === "actor") {
21062
+ expanded.add(node.id);
21063
+ }
21064
+ });
21065
+ if (selectedNodeId) {
21066
+ for (const node of findSessionNodePath([activeSession], selectedNodeId)) {
21067
+ if (node.children.length) {
21068
+ expanded.add(node.id);
21069
+ }
21070
+ }
21071
+ }
21072
+ return expanded;
21073
+ }
20998
21074
  function deriveSessionNavItem(node, traceById) {
20999
21075
  const traces = node.traceIds.map((traceId) => traceById.get(traceId)).filter((trace) => Boolean(trace));
21000
21076
  const latestTrace = getLatestTrace(traces);
@@ -21110,6 +21186,12 @@ function formatCompactTimestamp(value) {
21110
21186
  minute: "2-digit"
21111
21187
  });
21112
21188
  }
21189
+ function visitSessionTree(nodes, visitor) {
21190
+ for (const node of nodes) {
21191
+ visitor(node);
21192
+ visitSessionTree(node.children, visitor);
21193
+ }
21194
+ }
21113
21195
  var init_session_nav = __esm({
21114
21196
  "src/session-nav.ts"() {
21115
21197
  }
@@ -21122,14 +21204,9 @@ function App() {
21122
21204
  hierarchy: { filtered: 0, rootNodes: [], total: 0 },
21123
21205
  traces: { filtered: 0, items: [], total: 0 }
21124
21206
  });
21125
- const [navMode, setNavMode] = (0, import_react3.useState)("sessions");
21126
21207
  const [theme, setTheme] = (0, import_react3.useState)(() => resolvePreferredTheme());
21127
21208
  const [eventsConnected, setEventsConnected] = (0, import_react3.useState)(false);
21128
21209
  const [expandedNodeOverrides, setExpandedNodeOverrides] = (0, import_react3.useState)({});
21129
- const [collapsedTraceGroups, setCollapsedTraceGroups] = (0, import_react3.useState)({});
21130
- const [selectedSessionId, setSelectedSessionId] = (0, import_react3.useState)(
21131
- null
21132
- );
21133
21210
  const [selectedNodeId, setSelectedNodeId] = (0, import_react3.useState)(null);
21134
21211
  const [selectedTraceId, setSelectedTraceId] = (0, import_react3.useState)(null);
21135
21212
  const [detail, setDetail] = (0, import_react3.useState)(null);
@@ -21194,9 +21271,6 @@ function App() {
21194
21271
  (0, import_react3.startTransition)(() => {
21195
21272
  setData({ traces, hierarchy });
21196
21273
  setAllSessionCount(nextAllSessionCount);
21197
- setSelectedSessionId(
21198
- (current) => current ?? hierarchy.rootNodes[0]?.id ?? null
21199
- );
21200
21274
  });
21201
21275
  } finally {
21202
21276
  refreshInFlightRef.current = false;
@@ -21314,7 +21388,6 @@ function App() {
21314
21388
  traces: { filtered: 0, items: [], total: 0 }
21315
21389
  });
21316
21390
  setAllSessionCount(0);
21317
- setSelectedSessionId(null);
21318
21391
  setSelectedNodeId(null);
21319
21392
  setSelectedTraceId(null);
21320
21393
  setDetail(null);
@@ -21322,34 +21395,36 @@ function App() {
21322
21395
  });
21323
21396
  const handleSseMessage = (0, import_react3.useEffectEvent)((data2) => {
21324
21397
  const payload = parseEvent(data2);
21398
+ const nextTrace = payload?.span;
21399
+ const nextTraceId = payload?.spanId;
21325
21400
  if (payload?.type === "ui:reload") {
21326
21401
  window.location.reload();
21327
21402
  return;
21328
21403
  }
21329
- if (payload?.trace && selectedTraceId && payload.traceId === selectedTraceId) {
21330
- (0, import_react3.startTransition)(() => setDetail(payload.trace));
21404
+ if (nextTrace && selectedTraceId && nextTraceId === selectedTraceId) {
21405
+ (0, import_react3.startTransition)(() => setDetail(nextTrace));
21331
21406
  }
21332
21407
  if (!queryString) {
21333
- if (payload?.type === "trace:update" && payload.trace) {
21334
- applyIncrementalTraceUpdate(payload.trace);
21408
+ if ((payload?.type === "span:update" || payload?.type === "span:end") && nextTrace) {
21409
+ applyIncrementalTraceUpdate(nextTrace);
21335
21410
  return;
21336
21411
  }
21337
- if (payload?.type === "trace:add" && payload.trace) {
21338
- applyIncrementalTraceAdd(payload.trace);
21412
+ if (payload?.type === "span:start" && nextTrace) {
21413
+ applyIncrementalTraceAdd(nextTrace);
21339
21414
  scheduleRefresh(180);
21340
21415
  return;
21341
21416
  }
21342
- if (payload?.type === "trace:evict") {
21343
- applyIncrementalTraceEvict(payload.traceId);
21417
+ if (payload?.type === "span:evict") {
21418
+ applyIncrementalTraceEvict(nextTraceId);
21344
21419
  scheduleRefresh(180);
21345
21420
  return;
21346
21421
  }
21347
- if (payload?.type === "trace:clear") {
21422
+ if (payload?.type === "span:clear") {
21348
21423
  clearIncrementalState();
21349
21424
  return;
21350
21425
  }
21351
21426
  }
21352
- scheduleRefresh(payload?.type === "trace:update" ? 700 : 180);
21427
+ scheduleRefresh(payload?.type === "span:update" || payload?.type === "span:end" ? 700 : 180);
21353
21428
  });
21354
21429
  (0, import_react3.useEffect)(() => {
21355
21430
  const events = new EventSource("/api/events");
@@ -21373,75 +21448,61 @@ function App() {
21373
21448
  () => getMaxDurationMs(traceItems),
21374
21449
  [traceItems]
21375
21450
  );
21376
- const traceGroups = (0, import_react3.useMemo)(
21377
- () => groupTracesForNav(traceItems),
21378
- [traceItems]
21379
- );
21380
21451
  const sessionNodes = (0, import_react3.useMemo)(
21381
- () => data.hierarchy.rootNodes.filter((node) => node.type === "session"),
21382
- [data.hierarchy.rootNodes]
21383
- );
21384
- const sessionNodeById = (0, import_react3.useMemo)(
21385
- () => new Map(sessionNodes.map((node) => [node.id, node])),
21386
- [sessionNodes]
21452
+ () => sortSessionNodesForNav(
21453
+ data.hierarchy.rootNodes.filter((node) => node.type === "session"),
21454
+ traceById
21455
+ ),
21456
+ [data.hierarchy.rootNodes, traceById]
21387
21457
  );
21388
21458
  const sessionNavItems = (0, import_react3.useMemo)(
21389
21459
  () => deriveSessionNavItems(sessionNodes, traceById),
21390
21460
  [sessionNodes, traceById]
21391
21461
  );
21462
+ const sessionNavById = (0, import_react3.useMemo)(
21463
+ () => new Map(sessionNavItems.map((item) => [item.id, item])),
21464
+ [sessionNavItems]
21465
+ );
21392
21466
  const hasActiveFilters = Boolean(
21393
21467
  filters.search || filters.status || filters.kind || filters.tags
21394
21468
  );
21395
- const filteredSessionCount = sessionNavItems.length;
21469
+ const filteredSessionCount = sessionNodes.length;
21396
21470
  const hasRecordedSessions = allSessionCount > 0;
21397
21471
  const showFilteredSessionEmptyState = hasActiveFilters && hasRecordedSessions && filteredSessionCount === 0;
21472
+ const selectedNodePath = (0, import_react3.useMemo)(
21473
+ () => selectedNodeId ? findSessionNodePath(sessionNodes, selectedNodeId) : [],
21474
+ [sessionNodes, selectedNodeId]
21475
+ );
21476
+ const selectedTracePath = (0, import_react3.useMemo)(
21477
+ () => selectedTraceId ? findSessionNodePath(sessionNodes, toTraceNodeId(selectedTraceId)) : [],
21478
+ [sessionNodes, selectedTraceId]
21479
+ );
21480
+ const selectedPathIds = (0, import_react3.useMemo)(
21481
+ () => new Set(selectedNodePath.map((node) => node.id)),
21482
+ [selectedNodePath]
21483
+ );
21398
21484
  const selectedSessionNode = (0, import_react3.useMemo)(
21399
- () => selectedSessionId ? sessionNodeById.get(selectedSessionId) ?? null : (sessionNavItems[0] && sessionNodeById.get(sessionNavItems[0].id)) ?? null,
21400
- [selectedSessionId, sessionNavItems, sessionNodeById]
21485
+ () => (selectedNodePath[0]?.type === "session" ? selectedNodePath[0] : null) ?? (selectedTracePath[0]?.type === "session" ? selectedTracePath[0] : null) ?? sessionNodes[0] ?? null,
21486
+ [selectedNodePath, selectedTracePath, sessionNodes]
21401
21487
  );
21402
21488
  const selectedTraceSummary = (0, import_react3.useMemo)(
21403
21489
  () => selectedTraceId ? traceById.get(selectedTraceId) ?? null : null,
21404
21490
  [selectedTraceId, traceById]
21405
21491
  );
21406
21492
  (0, import_react3.useEffect)(() => {
21407
- if (!selectedSessionId || !sessionNodeById.has(selectedSessionId)) {
21408
- (0, import_react3.startTransition)(
21409
- () => setSelectedSessionId(sessionNavItems[0]?.id ?? null)
21410
- );
21411
- }
21412
- }, [selectedSessionId, sessionNavItems, sessionNodeById]);
21413
- (0, import_react3.useEffect)(() => {
21414
- if (navMode !== "sessions") {
21415
- return;
21416
- }
21417
- const currentSelectedNode = selectedNodeId && selectedSessionNode ? findNodeById([selectedSessionNode], selectedNodeId) : null;
21418
- const fallbackTraceId = getNewestTraceId(selectedSessionNode);
21419
- const fallbackNodeId = currentSelectedNode?.id ?? (fallbackTraceId ? toTraceNodeId(fallbackTraceId) : selectedSessionNode?.id ?? null);
21420
- if (fallbackNodeId !== selectedNodeId) {
21421
- (0, import_react3.startTransition)(() => setSelectedNodeId(fallbackNodeId));
21422
- }
21423
- }, [navMode, selectedNodeId, selectedSessionNode]);
21424
- (0, import_react3.useEffect)(() => {
21425
- const hasSelectedTrace = selectedTraceId ? traceItems.some((trace) => trace.id === selectedTraceId) : false;
21426
- if (navMode === "traces") {
21427
- if (!hasSelectedTrace) {
21428
- (0, import_react3.startTransition)(() => setSelectedTraceId(traceItems[0]?.id ?? null));
21429
- }
21493
+ const nextSelection = resolveSessionTreeSelection(
21494
+ sessionNodes,
21495
+ selectedNodeId,
21496
+ selectedTraceId
21497
+ );
21498
+ if (nextSelection.selectedNodeId === selectedNodeId && nextSelection.selectedTraceId === selectedTraceId) {
21430
21499
  return;
21431
21500
  }
21432
- const fallbackNode = (selectedNodeId ? findNodeById(data.hierarchy.rootNodes, selectedNodeId) : null) ?? selectedSessionNode;
21433
- const nextTraceId = hasSelectedTrace ? selectedTraceId : getNewestTraceId(fallbackNode);
21434
- if (nextTraceId !== selectedTraceId) {
21435
- (0, import_react3.startTransition)(() => setSelectedTraceId(nextTraceId));
21436
- }
21437
- }, [
21438
- data.hierarchy.rootNodes,
21439
- navMode,
21440
- selectedNodeId,
21441
- selectedSessionNode,
21442
- selectedTraceId,
21443
- traceItems
21444
- ]);
21501
+ (0, import_react3.startTransition)(() => {
21502
+ setSelectedNodeId(nextSelection.selectedNodeId);
21503
+ setSelectedTraceId(nextSelection.selectedTraceId);
21504
+ });
21505
+ }, [selectedNodeId, selectedTraceId, sessionNodes]);
21445
21506
  const loadDetail = (0, import_react3.useEffectEvent)(async (traceId) => {
21446
21507
  const requestId = detailRequestRef.current + 1;
21447
21508
  detailRequestRef.current = requestId;
@@ -21468,43 +21529,13 @@ function App() {
21468
21529
  void loadDetail(selectedTraceId);
21469
21530
  }, [selectedTraceId]);
21470
21531
  const defaultExpandedNodeIds = (0, import_react3.useMemo)(
21471
- () => getDefaultExpandedNodeIds(data.hierarchy.rootNodes),
21472
- [data.hierarchy.rootNodes]
21473
- );
21474
- const selectedNodePath = (0, import_react3.useMemo)(
21475
- () => selectedNodeId ? findNodePath(data.hierarchy.rootNodes, selectedNodeId) : [],
21476
- [data.hierarchy.rootNodes, selectedNodeId]
21477
- );
21478
- const selectedTimelineModel = (0, import_react3.useMemo)(
21479
- () => buildHierarchyTimelineModel(
21480
- selectedSessionNode,
21481
- traceById,
21482
- selectedNodeId,
21483
- selectedNodePath,
21484
- selectedTraceId
21532
+ () => getDefaultExpandedSessionTreeNodeIds(
21533
+ sessionNodes,
21534
+ selectedSessionNode?.id ?? null,
21535
+ selectedNodeId
21485
21536
  ),
21486
- [
21487
- selectedNodeId,
21488
- selectedNodePath,
21489
- selectedSessionNode,
21490
- selectedTraceId,
21491
- traceById
21492
- ]
21537
+ [selectedNodeId, selectedSessionNode, sessionNodes]
21493
21538
  );
21494
- (0, import_react3.useEffect)(() => {
21495
- if (!selectedNodePath.length) {
21496
- return;
21497
- }
21498
- (0, import_react3.startTransition)(() => {
21499
- setExpandedNodeOverrides((current) => {
21500
- const next = { ...current };
21501
- for (const node of selectedNodePath.slice(0, -1)) {
21502
- next[node.id] = true;
21503
- }
21504
- return next;
21505
- });
21506
- });
21507
- }, [selectedNodePath]);
21508
21539
  const activeTabJsonMode = tabModes[detailTab] ?? "formatted";
21509
21540
  const activeTagFilterCount = countTagFilters(filters.tags);
21510
21541
  const onFilterChange = (key, value) => {
@@ -21543,24 +21574,13 @@ function App() {
21543
21574
  }));
21544
21575
  });
21545
21576
  };
21546
- const toggleTraceGroupCollapse = (groupId) => {
21547
- (0, import_react3.startTransition)(() => {
21548
- setCollapsedTraceGroups((current) => ({
21549
- ...current,
21550
- [groupId]: !current[groupId]
21551
- }));
21552
- });
21553
- };
21554
21577
  const resetFilters = () => {
21555
- const nextSessionNode = (sessionNavItems[0] && sessionNodeById.get(sessionNavItems[0].id)) ?? null;
21556
21578
  (0, import_react3.startTransition)(() => {
21557
21579
  setFilters(INITIAL_FILTERS);
21558
21580
  setShowAdvancedFilters(false);
21559
- setSelectedSessionId(nextSessionNode?.id ?? null);
21560
- setSelectedNodeId(nextSessionNode?.id ?? null);
21561
- setSelectedTraceId(
21562
- navMode === "traces" ? traceItems[0]?.id ?? null : getNewestTraceId(nextSessionNode)
21563
- );
21581
+ setSelectedNodeId(null);
21582
+ setSelectedTraceId(null);
21583
+ setDetailTab("conversation");
21564
21584
  });
21565
21585
  };
21566
21586
  const clearTraces = async () => {
@@ -21594,78 +21614,21 @@ function App() {
21594
21614
  () => setIsSessionsPanelCollapsed((current) => !current)
21595
21615
  );
21596
21616
  };
21597
- const showSessionsMode = () => {
21598
- const tracePath = selectedTraceId ? findNodePath(data.hierarchy.rootNodes, `trace:${selectedTraceId}`) : [];
21599
- const fallbackSessionNode = tracePath[0] ?? selectedSessionNode ?? ((sessionNavItems[0] && sessionNodeById.get(sessionNavItems[0].id)) ?? null);
21600
- const fallbackNodeId = tracePath[tracePath.length - 1]?.id ?? selectedNodeId ?? fallbackSessionNode?.id ?? null;
21601
- const nextTraceId = selectedTraceId && traceById.has(selectedTraceId) ? selectedTraceId : getNewestTraceId(fallbackSessionNode);
21602
- (0, import_react3.startTransition)(() => {
21603
- setNavMode("sessions");
21604
- setSelectedSessionId(fallbackSessionNode?.id ?? null);
21605
- setSelectedNodeId(fallbackNodeId);
21606
- setSelectedTraceId(nextTraceId);
21607
- setDetailTab("conversation");
21608
- });
21609
- };
21610
- const showTracesMode = () => {
21611
- (0, import_react3.startTransition)(() => {
21612
- const nextTraceId = selectedTraceId && traceItems.some((trace) => trace.id === selectedTraceId) ? selectedTraceId : traceItems[0]?.id ?? null;
21613
- setNavMode("traces");
21614
- setSelectedTraceId(nextTraceId);
21615
- setSelectedNodeId(nextTraceId ? `trace:${nextTraceId}` : selectedNodeId);
21616
- setDetailTab("conversation");
21617
- if (nextTraceId) {
21618
- const nextTrace = traceById.get(nextTraceId);
21619
- if (nextTrace?.hierarchy.sessionId) {
21620
- setSelectedSessionId(toSessionNodeId(nextTrace.hierarchy.sessionId));
21621
- }
21622
- }
21623
- });
21624
- };
21625
- const selectTraceFromList = (traceId) => {
21626
- const trace = traceById.get(traceId);
21627
- (0, import_react3.startTransition)(() => {
21628
- setNavMode("traces");
21629
- setSelectedTraceId(traceId);
21630
- setSelectedNodeId(`trace:${traceId}`);
21631
- setDetailTab("conversation");
21632
- if (trace?.hierarchy.sessionId) {
21633
- setSelectedSessionId(toSessionNodeId(trace.hierarchy.sessionId));
21634
- }
21635
- });
21636
- };
21637
21617
  const handleHierarchySelect = (node) => {
21638
- const nodePath = findNodePath(data.hierarchy.rootNodes, node.id);
21639
- const nextTraceId = node.type === "trace" ? node.meta.traceId ?? node.traceIds[0] ?? null : getNewestTraceId(node);
21640
- const nextSessionId = nodePath[0]?.type === "session" ? nodePath[0].id : selectedSessionId;
21641
- const nextSelectedNodeId = node.type === "trace" ? node.id : nextTraceId ? toTraceNodeId(nextTraceId) : node.id;
21618
+ const nextTraceId = node.type === "trace" ? node.meta.traceId ?? node.traceIds[0] ?? null : getNewestTraceIdForNode(node);
21642
21619
  (0, import_react3.startTransition)(() => {
21643
- setNavMode("sessions");
21644
- setSelectedSessionId(nextSessionId ?? null);
21645
- setSelectedNodeId(nextSelectedNodeId);
21620
+ setSelectedNodeId(node.id);
21646
21621
  setSelectedTraceId(nextTraceId);
21647
21622
  setDetailTab("conversation");
21648
21623
  });
21649
21624
  };
21650
- const handleTimelineSelect = (nodeId) => {
21651
- const scopeNodes = selectedSessionNode ? [selectedSessionNode] : data.hierarchy.rootNodes;
21652
- const node = findNodeById(scopeNodes, nodeId) ?? findNodeById(data.hierarchy.rootNodes, nodeId);
21653
- if (!node) {
21654
- return;
21655
- }
21656
- handleHierarchySelect(node);
21657
- };
21658
21625
  const navigateToHierarchyNode = (nodeId) => {
21659
- const node = findNodeById(data.hierarchy.rootNodes, nodeId);
21626
+ const node = findSessionNodeById(sessionNodes, nodeId);
21660
21627
  if (!node) {
21661
21628
  return;
21662
21629
  }
21663
- const nodePath = findNodePath(data.hierarchy.rootNodes, nodeId);
21664
- const nextSessionId = nodePath[0]?.type === "session" ? nodePath[0].id : selectedSessionId;
21665
- const nextTraceId = selectedTraceId && node.traceIds.includes(selectedTraceId) ? selectedTraceId : getNewestTraceId(node);
21630
+ const nextTraceId = selectedTraceId && node.traceIds.includes(selectedTraceId) ? selectedTraceId : getNewestTraceIdForNode(node);
21666
21631
  (0, import_react3.startTransition)(() => {
21667
- setNavMode("sessions");
21668
- setSelectedSessionId(nextSessionId ?? null);
21669
21632
  setSelectedNodeId(nodeId);
21670
21633
  if (nextTraceId !== selectedTraceId) {
21671
21634
  setSelectedTraceId(nextTraceId);
@@ -21674,17 +21637,6 @@ function App() {
21674
21637
  };
21675
21638
  const detailTabs = buildDetailTabs(detail);
21676
21639
  const activeTab = detailTabs.some((tab) => tab.id === detailTab) ? detailTab : detailTabs[0]?.id ?? "conversation";
21677
- const handleSessionChange = (sessionId) => {
21678
- const sessionNode = sessionNodeById.get(sessionId) ?? null;
21679
- const nextTraceId = getNewestTraceId(sessionNode);
21680
- (0, import_react3.startTransition)(() => {
21681
- setNavMode("sessions");
21682
- setSelectedSessionId(sessionId);
21683
- setSelectedNodeId(nextTraceId ? toTraceNodeId(nextTraceId) : sessionId);
21684
- setSelectedTraceId(nextTraceId);
21685
- setDetailTab("conversation");
21686
- });
21687
- };
21688
21640
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "app-shell", children: [
21689
21641
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BackgroundGlow, {}),
21690
21642
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "app-frame", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "board-shell inspector-shell", children: [
@@ -21813,46 +21765,45 @@ function App() {
21813
21765
  isSessionsPanelCollapsed && "is-sidebar-collapsed"
21814
21766
  ),
21815
21767
  children: [
21816
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { className: "sidebar-card session-sidebar-card inspector-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-sidebar-shell", children: [
21817
- sessionNavItems.length || hasActiveFilters || hasRecordedSessions ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21818
- SessionNavList,
21819
- {
21820
- hasActiveFilters,
21821
- items: sessionNavItems,
21822
- onChange: handleSessionChange,
21823
- totalCount: allSessionCount,
21824
- selectedId: selectedSessionNode?.id ?? null
21825
- }
21826
- ) : null,
21827
- selectedTimelineModel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21828
- HierarchyTimelineOverview,
21829
- {
21830
- axisStops: COMPACT_TIMELINE_AXIS_STOPS,
21831
- model: selectedTimelineModel,
21832
- onSelectRow: handleTimelineSelect
21833
- }
21834
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-sidebar-empty", children: showFilteredSessionEmptyState ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-sidebar-empty-state", children: [
21835
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21836
- EmptyState,
21837
- {
21838
- icon: Funnel,
21839
- title: "No sessions match the current filters",
21840
- description: "Try adjusting the search, status, kind, or tag filters to broaden the result set."
21841
- }
21842
- ),
21843
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, { variant: "outline", onClick: resetFilters, children: [
21844
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "data-icon": "inline-start" }),
21845
- "Clear filters"
21846
- ] })
21847
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21768
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { className: "sidebar-card session-sidebar-card inspector-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-sidebar-shell", children: sessionNodes.length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21769
+ SessionTreeNavigator,
21770
+ {
21771
+ defaultExpandedNodeIds,
21772
+ expandedNodeOverrides,
21773
+ hasActiveFilters,
21774
+ items: sessionNavItems,
21775
+ maxDurationMs: navigatorMaxDurationMs,
21776
+ nodes: sessionNodes,
21777
+ onSelect: handleHierarchySelect,
21778
+ onToggle: toggleNodeExpansion,
21779
+ selectedNodeId,
21780
+ selectedPathIds,
21781
+ selectedTraceId,
21782
+ sessionNavById,
21783
+ totalCount: allSessionCount,
21784
+ traceById
21785
+ }
21786
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-sidebar-empty", children: showFilteredSessionEmptyState ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-sidebar-empty-state", children: [
21787
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21848
21788
  EmptyState,
21849
21789
  {
21850
- icon: Network,
21851
- title: "No sessions yet",
21852
- description: "Trigger any traced LLM call and the session timeline will appear here."
21790
+ icon: Funnel,
21791
+ title: "No sessions match the current filters",
21792
+ description: "Try adjusting the search, status, kind, or tag filters to broaden the result set."
21853
21793
  }
21854
- ) })
21855
- ] }) }),
21794
+ ),
21795
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, { variant: "outline", onClick: resetFilters, children: [
21796
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "data-icon": "inline-start" }),
21797
+ "Clear filters"
21798
+ ] })
21799
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21800
+ EmptyState,
21801
+ {
21802
+ icon: Network,
21803
+ title: "No sessions yet",
21804
+ description: "Trigger any traced LLM call and the session tree will appear here."
21805
+ }
21806
+ ) }) }) }),
21856
21807
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { className: "timeline-card content-card inspector-card", children: selectedTraceId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21857
21808
  TraceDetailPanel,
21858
21809
  {
@@ -21865,20 +21816,18 @@ function App() {
21865
21816
  onApplyTraceFilter: applyTraceFilter,
21866
21817
  onNavigateHierarchyNode: navigateToHierarchyNode,
21867
21818
  onTabChange: (value) => setDetailTab(value),
21868
- onSelectTimelineNode: handleTimelineSelect,
21869
21819
  onToggleJsonMode: (tabId) => (0, import_react3.startTransition)(() => {
21870
21820
  setTabModes((current) => ({
21871
21821
  ...current,
21872
21822
  [tabId]: (current[tabId] ?? "formatted") === "formatted" ? "raw" : "formatted"
21873
21823
  }));
21874
- }),
21875
- timelineModel: null
21824
+ })
21876
21825
  }
21877
21826
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, { className: "content-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21878
21827
  EmptyState,
21879
21828
  {
21880
21829
  icon: ArrowUpRight,
21881
- title: "Select a trace from the session timeline",
21830
+ title: "Select a trace from the session tree",
21882
21831
  description: "Choose any call on the left to inspect the full request, response, context, and stream details."
21883
21832
  }
21884
21833
  ) }) })
@@ -21931,63 +21880,51 @@ function ThemeSwitcher({
21931
21880
  )
21932
21881
  ] });
21933
21882
  }
21934
- function SessionNavList({
21883
+ function SessionTreeNavigator({
21884
+ defaultExpandedNodeIds,
21885
+ expandedNodeOverrides,
21935
21886
  hasActiveFilters,
21936
21887
  items,
21937
- onChange,
21938
- selectedId,
21939
- totalCount
21888
+ maxDurationMs,
21889
+ nodes,
21890
+ onSelect,
21891
+ onToggle,
21892
+ selectedNodeId,
21893
+ selectedPathIds,
21894
+ selectedTraceId,
21895
+ sessionNavById,
21896
+ totalCount,
21897
+ traceById
21940
21898
  }) {
21941
21899
  const filteredCount = items.length;
21942
21900
  const countLabel = hasActiveFilters && totalCount > filteredCount ? `${filteredCount} of ${totalCount} sessions` : formatCountLabel(
21943
21901
  hasActiveFilters ? filteredCount : Math.max(filteredCount, totalCount),
21944
21902
  "session"
21945
21903
  );
21946
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-nav-section", children: [
21947
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-nav-header", children: [
21948
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-nav-title-row", children: [
21949
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-nav-title", children: "Sessions" }),
21950
- hasActiveFilters ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", className: "session-nav-filter-badge", children: "Filtered" }) : null
21904
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-section", children: [
21905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-header", children: [
21906
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-title-row", children: [
21907
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-title", children: "Sessions" }),
21908
+ hasActiveFilters ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", className: "session-tree-filter-badge", children: "Filtered" }) : null
21951
21909
  ] }),
21952
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-nav-meta", children: countLabel })
21910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-meta", children: countLabel })
21953
21911
  ] }),
21954
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-nav-list", role: "list", "aria-label": "Sessions", children: items.map((item) => {
21955
- const costLabel = formatUsdCost(item.costUsd);
21956
- const detailLabel = formatList([formatCountLabel(item.callCount, "call"), costLabel]) || formatCountLabel(item.callCount, "call");
21957
- const badge = getSessionNavBadge(item);
21958
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
21959
- "button",
21960
- {
21961
- type: "button",
21962
- role: "listitem",
21963
- className: cn(
21964
- "session-nav-card",
21965
- item.id === selectedId && "is-active"
21966
- ),
21967
- "aria-pressed": item.id === selectedId,
21968
- onClick: () => onChange(item.id),
21969
- children: [
21970
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-nav-card-header", children: [
21971
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21972
- "div",
21973
- {
21974
- className: "session-nav-card-title",
21975
- title: item.primaryLabel,
21976
- children: item.primaryLabel
21977
- }
21978
- ),
21979
- item.latestTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-nav-card-time", children: item.latestTimestamp }) : null
21980
- ] }),
21981
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-nav-card-meta", children: detailLabel }),
21982
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-nav-card-footer", children: [
21983
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-nav-card-id", children: item.shortSessionId }),
21984
- badge
21985
- ] })
21986
- ]
21987
- },
21988
- item.id
21989
- );
21990
- }) })
21912
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21913
+ HierarchyTree,
21914
+ {
21915
+ defaultExpandedNodeIds,
21916
+ expandedNodeOverrides,
21917
+ maxDurationMs,
21918
+ nodes,
21919
+ onSelect,
21920
+ onToggle,
21921
+ selectedNodeId,
21922
+ selectedPathIds,
21923
+ selectedTraceId,
21924
+ sessionNavById,
21925
+ traceById
21926
+ }
21927
+ ) })
21991
21928
  ] });
21992
21929
  }
21993
21930
  function getSessionNavBadge(item) {
@@ -22009,6 +21946,374 @@ function BackgroundGlow() {
22009
21946
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid-noise" })
22010
21947
  ] });
22011
21948
  }
21949
+ function HierarchyTree({
21950
+ defaultExpandedNodeIds,
21951
+ expandedNodeOverrides,
21952
+ maxDurationMs,
21953
+ nodes,
21954
+ onSelect,
21955
+ onToggle,
21956
+ selectedNodeId,
21957
+ selectedPathIds,
21958
+ selectedTraceId,
21959
+ sessionNavById,
21960
+ traceById
21961
+ }) {
21962
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-root", children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21963
+ HierarchyTreeNode,
21964
+ {
21965
+ defaultExpandedNodeIds,
21966
+ depth: 0,
21967
+ expandedNodeOverrides,
21968
+ maxDurationMs,
21969
+ node,
21970
+ onSelect,
21971
+ onToggle,
21972
+ selectedNodeId,
21973
+ selectedPathIds,
21974
+ selectedTraceId,
21975
+ sessionNavById,
21976
+ traceById
21977
+ },
21978
+ node.id
21979
+ )) });
21980
+ }
21981
+ function HierarchyTreeNode({
21982
+ defaultExpandedNodeIds,
21983
+ depth,
21984
+ expandedNodeOverrides,
21985
+ maxDurationMs,
21986
+ node,
21987
+ onSelect,
21988
+ onToggle,
21989
+ selectedNodeId,
21990
+ selectedPathIds,
21991
+ selectedTraceId,
21992
+ sessionNavById,
21993
+ traceById
21994
+ }) {
21995
+ const isExpandable = node.children.length > 0;
21996
+ const isForcedExpanded = isExpandable && selectedPathIds.has(node.id);
21997
+ const expandedOverride = expandedNodeOverrides[node.id];
21998
+ const isExpanded = isExpandable && (expandedOverride ?? (isForcedExpanded || defaultExpandedNodeIds.has(node.id)));
21999
+ const isInPath = selectedPathIds.has(node.id);
22000
+ const nodeCopy = getHierarchyNodeCopy(node, traceById);
22001
+ const trace = node.meta.traceId ? traceById.get(node.meta.traceId) ?? null : null;
22002
+ const sessionNavItem = node.type === "session" ? sessionNavById.get(node.id) ?? null : null;
22003
+ if (node.type === "trace" && trace) {
22004
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22005
+ TraceHierarchyLeaf,
22006
+ {
22007
+ depth,
22008
+ maxDurationMs,
22009
+ node,
22010
+ nodeCopy,
22011
+ onSelect,
22012
+ inPath: isInPath,
22013
+ selected: selectedNodeId === node.id,
22014
+ selectedTrace: selectedTraceId === trace.id,
22015
+ trace
22016
+ }
22017
+ );
22018
+ }
22019
+ if (sessionNavItem) {
22020
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22021
+ SessionHierarchyBranch,
22022
+ {
22023
+ depth,
22024
+ defaultExpandedNodeIds,
22025
+ expandedNodeOverrides,
22026
+ isExpanded,
22027
+ item: sessionNavItem,
22028
+ maxDurationMs,
22029
+ node,
22030
+ onSelect,
22031
+ onToggle,
22032
+ selected: selectedNodeId === node.id,
22033
+ selectedNodeId,
22034
+ selectedPathIds,
22035
+ selectedTraceId,
22036
+ sessionNavById,
22037
+ traceById
22038
+ }
22039
+ );
22040
+ }
22041
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22042
+ "div",
22043
+ {
22044
+ className: "tree-node-wrap",
22045
+ style: { "--depth": String(depth) },
22046
+ children: [
22047
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22048
+ "div",
22049
+ {
22050
+ className: clsx_default(
22051
+ "tree-node-card",
22052
+ selectedNodeId === node.id && "is-active",
22053
+ isInPath && "is-in-path",
22054
+ node.type === "trace" && "is-trace"
22055
+ ),
22056
+ children: [
22057
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22058
+ "button",
22059
+ {
22060
+ type: "button",
22061
+ className: clsx_default("tree-node-toggle", !isExpandable && "is-static"),
22062
+ disabled: !isExpandable,
22063
+ onClick: () => {
22064
+ if (isExpandable) {
22065
+ onToggle(node.id);
22066
+ }
22067
+ },
22068
+ "aria-label": isExpandable ? `${isExpanded ? "Collapse" : "Expand"} ${nodeCopy.label}` : void 0,
22069
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: clsx_default(isExpanded && "is-open") })
22070
+ }
22071
+ ),
22072
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22073
+ "button",
22074
+ {
22075
+ type: "button",
22076
+ className: "tree-node-select",
22077
+ onClick: () => onSelect(node),
22078
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
22079
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-heading", children: [
22080
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
22081
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22082
+ Badge,
22083
+ {
22084
+ variant: "secondary",
22085
+ className: "tree-node-inline-badge",
22086
+ semantic: nodeCopy.badge,
22087
+ children: nodeCopy.badge
22088
+ }
22089
+ )
22090
+ ] }),
22091
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: nodeCopy.meta })
22092
+ ] })
22093
+ }
22094
+ )
22095
+ ]
22096
+ }
22097
+ ),
22098
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22099
+ HierarchyTreeNode,
22100
+ {
22101
+ defaultExpandedNodeIds,
22102
+ depth: depth + 1,
22103
+ expandedNodeOverrides,
22104
+ maxDurationMs,
22105
+ node: child,
22106
+ onSelect,
22107
+ onToggle,
22108
+ selectedNodeId,
22109
+ selectedPathIds,
22110
+ selectedTraceId,
22111
+ sessionNavById,
22112
+ traceById
22113
+ },
22114
+ child.id
22115
+ )) }) : null
22116
+ ]
22117
+ }
22118
+ );
22119
+ }
22120
+ function SessionHierarchyBranch({
22121
+ defaultExpandedNodeIds,
22122
+ depth,
22123
+ expandedNodeOverrides,
22124
+ isExpanded,
22125
+ item,
22126
+ maxDurationMs,
22127
+ node,
22128
+ onSelect,
22129
+ onToggle,
22130
+ selected,
22131
+ selectedNodeId,
22132
+ selectedPathIds,
22133
+ selectedTraceId,
22134
+ sessionNavById,
22135
+ traceById
22136
+ }) {
22137
+ const detailLabel = formatList([
22138
+ formatCountLabel(item.callCount, "call"),
22139
+ formatUsdCost(item.costUsd)
22140
+ ]) || formatCountLabel(item.callCount, "call");
22141
+ const sessionSelectedPath = selectedNodeId ? findSessionNodePath([node], selectedNodeId) : [];
22142
+ const sessionTimelineModel = buildHierarchyTimelineModel(
22143
+ node,
22144
+ traceById,
22145
+ selectedNodeId,
22146
+ sessionSelectedPath,
22147
+ selectedTraceId
22148
+ );
22149
+ const sessionTimelineRow = sessionTimelineModel?.rows[0] ?? null;
22150
+ const showEmbeddedTimeline = isExpanded && Boolean(sessionTimelineModel?.rows.length);
22151
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22152
+ "div",
22153
+ {
22154
+ className: "tree-node-wrap",
22155
+ style: { "--depth": String(depth) },
22156
+ children: [
22157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22158
+ "div",
22159
+ {
22160
+ className: clsx_default(
22161
+ "tree-node-card tree-session-card",
22162
+ selected && "is-active",
22163
+ selectedPathIds.has(node.id) && "is-in-path"
22164
+ ),
22165
+ children: [
22166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22167
+ "button",
22168
+ {
22169
+ type: "button",
22170
+ className: clsx_default(
22171
+ "tree-node-toggle",
22172
+ !node.children.length && "is-static"
22173
+ ),
22174
+ disabled: !node.children.length,
22175
+ onClick: () => {
22176
+ if (node.children.length) {
22177
+ onToggle(node.id);
22178
+ }
22179
+ },
22180
+ "aria-label": node.children.length ? `${isExpanded ? "Collapse" : "Expand"} ${item.primaryLabel}` : void 0,
22181
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: clsx_default(isExpanded && "is-open") })
22182
+ }
22183
+ ),
22184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22185
+ "button",
22186
+ {
22187
+ type: "button",
22188
+ className: "tree-node-select session-tree-select",
22189
+ onClick: () => onSelect(node),
22190
+ children: [
22191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy session-tree-copy", children: [
22192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-card-header", children: [
22193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-card-title", title: item.primaryLabel, children: item.primaryLabel }),
22194
+ item.latestTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-card-time", children: item.latestTimestamp }) : null
22195
+ ] }),
22196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: detailLabel }),
22197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-card-footer", children: [
22198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-card-id", children: item.shortSessionId }),
22199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-card-badges", children: [
22200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", children: "Session" }),
22201
+ getSessionNavBadge(item)
22202
+ ] })
22203
+ ] })
22204
+ ] }),
22205
+ sessionTimelineModel && sessionTimelineRow ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22206
+ SessionTreeTimelineBar,
22207
+ {
22208
+ model: sessionTimelineModel,
22209
+ row: sessionTimelineRow
22210
+ }
22211
+ ) : null
22212
+ ]
22213
+ }
22214
+ )
22215
+ ]
22216
+ }
22217
+ ),
22218
+ showEmbeddedTimeline && sessionTimelineModel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-timeline-shell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22219
+ "div",
22220
+ {
22221
+ className: "session-tree-timeline-list",
22222
+ role: "list",
22223
+ "aria-label": "Session timeline",
22224
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22225
+ HierarchyTimelineRows,
22226
+ {
22227
+ className: "is-embedded",
22228
+ model: sessionTimelineModel,
22229
+ onSelectRow: (nodeId) => {
22230
+ const selectedTimelineNode = findSessionNodeById([node], nodeId);
22231
+ if (selectedTimelineNode) {
22232
+ onSelect(selectedTimelineNode);
22233
+ }
22234
+ },
22235
+ rows: sessionTimelineModel.rows.slice(1)
22236
+ }
22237
+ )
22238
+ }
22239
+ ) }) : isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22240
+ HierarchyTreeNode,
22241
+ {
22242
+ defaultExpandedNodeIds,
22243
+ depth: depth + 1,
22244
+ expandedNodeOverrides,
22245
+ maxDurationMs,
22246
+ node: child,
22247
+ onSelect,
22248
+ onToggle,
22249
+ selectedNodeId,
22250
+ selectedPathIds,
22251
+ selectedTraceId,
22252
+ sessionNavById,
22253
+ traceById
22254
+ },
22255
+ child.id
22256
+ )) }) : null
22257
+ ]
22258
+ }
22259
+ );
22260
+ }
22261
+ function TraceHierarchyLeaf({
22262
+ depth,
22263
+ inPath,
22264
+ maxDurationMs,
22265
+ node,
22266
+ nodeCopy,
22267
+ onSelect,
22268
+ selected,
22269
+ selectedTrace,
22270
+ trace
22271
+ }) {
22272
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22273
+ "div",
22274
+ {
22275
+ className: "tree-node-wrap tree-trace-wrap",
22276
+ style: { "--depth": String(depth) },
22277
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22278
+ "div",
22279
+ {
22280
+ className: clsx_default(
22281
+ "tree-node-card is-trace",
22282
+ inPath && "is-in-path",
22283
+ selected && "is-active",
22284
+ selectedTrace && "is-detail-trace"
22285
+ ),
22286
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22287
+ "button",
22288
+ {
22289
+ type: "button",
22290
+ className: "tree-node-select tree-trace-select",
22291
+ onClick: () => onSelect(node),
22292
+ children: [
22293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
22294
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-nav-kicker", children: getTraceActorLabel(trace) }),
22295
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
22296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: formatList([
22297
+ formatTimelineTimestamp(trace.startedAt),
22298
+ nodeCopy.meta
22299
+ ]) })
22300
+ ] }),
22301
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22302
+ TraceElapsedBar,
22303
+ {
22304
+ compact: true,
22305
+ durationMs: trace.durationMs,
22306
+ maxDurationMs
22307
+ }
22308
+ )
22309
+ ]
22310
+ }
22311
+ )
22312
+ }
22313
+ )
22314
+ }
22315
+ );
22316
+ }
22012
22317
  function StatusBadge({
22013
22318
  status,
22014
22319
  onClick
@@ -22031,235 +22336,191 @@ function TraceMetricPill({
22031
22336
  }
22032
22337
  );
22033
22338
  }
22034
- function HierarchyTimelineOverview({
22035
- axisStops = TIMELINE_AXIS_STOPS,
22036
- model,
22037
- onSelectRow
22339
+ function TraceElapsedBar({
22340
+ compact = false,
22341
+ durationMs,
22342
+ maxDurationMs
22038
22343
  }) {
22039
- const listRef = (0, import_react3.useRef)(null);
22040
- const hasInitializedScrollRef = (0, import_react3.useRef)(false);
22041
- const totalCostLabel = formatCostSummaryLabel(
22042
- model.costUsd,
22043
- model.rows.length > 1
22044
- );
22045
- const hasTraceRows = model.rows.some((row) => row.type === "trace");
22046
- const activeRowId = model.rows.find((row) => row.isActive)?.id ?? model.rows.find((row) => row.isDetailTrace)?.id ?? null;
22047
- (0, import_react3.useEffect)(() => {
22048
- if (!activeRowId || !listRef.current) {
22049
- return;
22050
- }
22051
- if (!hasInitializedScrollRef.current) {
22052
- hasInitializedScrollRef.current = true;
22053
- return;
22054
- }
22055
- const list = listRef.current;
22056
- const target = [...list.querySelectorAll(
22057
- "[data-hierarchy-row-id]"
22058
- )].find((element) => element.dataset.hierarchyRowId === activeRowId);
22059
- if (!target) {
22060
- return;
22344
+ const durationLabel = formatElapsedLabel(durationMs);
22345
+ const scale = getElapsedScale(durationMs, maxDurationMs);
22346
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22347
+ "div",
22348
+ {
22349
+ className: cn(
22350
+ "trace-elapsed-bar",
22351
+ compact && "is-compact",
22352
+ durationMs === null && "is-pending"
22353
+ ),
22354
+ style: { "--elapsed-scale": String(scale) },
22355
+ children: [
22356
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-elapsed-track", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-elapsed-span" }) }),
22357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-elapsed-label", children: durationLabel })
22358
+ ]
22061
22359
  }
22062
- const viewportTop = list.scrollTop;
22063
- const viewportBottom = viewportTop + list.clientHeight;
22064
- const targetTop = target.offsetTop;
22065
- const targetBottom = targetTop + target.offsetHeight;
22066
- if (targetTop >= viewportTop && targetBottom <= viewportBottom) {
22067
- return;
22360
+ );
22361
+ }
22362
+ function SessionTreeTimelineBar({
22363
+ model,
22364
+ row
22365
+ }) {
22366
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22367
+ "span",
22368
+ {
22369
+ className: "session-tree-timeline",
22370
+ style: {
22371
+ "--session-tree-offset": model.durationMs > 0 ? String(row.offsetMs / model.durationMs) : "0",
22372
+ "--session-tree-span": model.durationMs > 0 ? String(row.durationMs / model.durationMs) : "1"
22373
+ },
22374
+ children: [
22375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-timeline-meta", children: [
22376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-timeline-start", children: formatTimelineTimestamp(row.startedAt) }),
22377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-timeline-duration", children: formatElapsedLabel(row.durationMs) })
22378
+ ] }),
22379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-timeline-track", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-timeline-bar" }) })
22380
+ ]
22068
22381
  }
22069
- const contextOffset = Math.max(
22070
- target.offsetHeight * 1.5,
22071
- list.clientHeight * 0.35
22382
+ );
22383
+ }
22384
+ function HierarchyTimelineRows({
22385
+ className,
22386
+ model,
22387
+ onSelectRow,
22388
+ rows
22389
+ }) {
22390
+ return rows.map((row) => {
22391
+ const isTraceRow = row.type === "trace";
22392
+ const rowClassName = cn(
22393
+ "hierarchy-timeline-row",
22394
+ className,
22395
+ isTraceRow ? "is-clickable" : "is-structure",
22396
+ row.depth === 0 && "is-root",
22397
+ row.isActive && "is-active",
22398
+ row.isDetailTrace && "is-detail-trace",
22399
+ row.isInPath && "is-in-path",
22400
+ `is-${row.type.replace(/[^a-z0-9-]/gi, "-")}`
22072
22401
  );
22073
- const nextTop = Math.max(0, targetTop - contextOffset);
22074
- list.scrollTo({ top: nextTop });
22075
- }, [activeRowId]);
22076
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-panel", children: [
22077
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-header", children: [
22078
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
22079
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-title", children: "Session timeline" }),
22080
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-meta", children: [
22081
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: model.sessionLabel }),
22082
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: formatTimelineTimestamp(model.startedAt) })
22083
- ] })
22084
- ] }),
22085
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-header-side", children: [
22086
- totalCostLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceMetricPill, { tone: "cost", children: totalCostLabel }) : null,
22087
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TraceMetricPill, { tone: "latency", children: formatElapsedLabel(model.durationMs) })
22088
- ] })
22089
- ] }),
22090
- hasTraceRows ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-hint", children: "Click a call row to inspect it on the right." }) : null,
22091
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-axis", "aria-hidden": "true", children: [
22092
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {}),
22093
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {}),
22094
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-axis-track", children: axisStops.map((stop) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22095
- "span",
22096
- {
22097
- className: "hierarchy-timeline-axis-tick",
22098
- style: { "--timeline-axis-offset": String(stop) },
22099
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-axis-label", children: formatElapsedLabel(model.durationMs * stop) })
22100
- },
22101
- stop
22102
- )) })
22103
- ] }),
22104
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22105
- "div",
22106
- {
22107
- ref: listRef,
22108
- className: "hierarchy-timeline-list",
22109
- role: "list",
22110
- "aria-label": "Nested session timeline",
22111
- children: model.rows.map((row) => {
22112
- const isTraceRow = row.type === "trace";
22113
- const rowClassName = cn(
22114
- "hierarchy-timeline-row",
22115
- isTraceRow ? "is-clickable" : "is-structure",
22116
- row.depth === 0 && "is-root",
22117
- row.isActive && "is-active",
22118
- row.isDetailTrace && "is-detail-trace",
22119
- row.isInPath && "is-in-path",
22120
- `is-${row.type.replace(/[^a-z0-9-]/gi, "-")}`
22121
- );
22122
- const rowStyle = {
22123
- "--timeline-depth": String(row.depth),
22124
- "--timeline-offset": String(
22125
- model.durationMs > 0 ? row.offsetMs / model.durationMs : 0
22126
- ),
22127
- "--timeline-span": String(
22128
- model.durationMs > 0 ? row.durationMs / model.durationMs : 1
22129
- )
22130
- };
22131
- const rowContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react3.Fragment, { children: [
22132
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-time", children: formatTimelineTimestamp(row.startedAt) }),
22133
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-branch", children: [
22134
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22135
- "div",
22136
- {
22137
- className: "hierarchy-timeline-row-gutter",
22138
- "aria-hidden": "true",
22139
- children: [
22140
- row.ancestorContinuations.map((continues, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22141
- "span",
22142
- {
22143
- className: cn(
22144
- "hierarchy-timeline-row-ancestor",
22145
- continues && "has-line"
22146
- ),
22147
- style: {
22148
- "--timeline-connector-index": String(index)
22149
- }
22150
- },
22151
- `${row.id}-ancestor-${index}`
22152
- )),
22153
- row.depth > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22154
- "span",
22155
- {
22156
- className: "hierarchy-timeline-row-connector",
22157
- style: {
22158
- "--timeline-connector-index": String(
22159
- row.depth - 1
22160
- )
22161
- },
22162
- children: [
22163
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-connector-top" }),
22164
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-connector-elbow" }),
22165
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22166
- "span",
22167
- {
22168
- className: cn(
22169
- "hierarchy-timeline-row-connector-bottom",
22170
- (row.hasVisibleChildren || !row.isLastSibling) && "has-line"
22171
- )
22172
- }
22173
- )
22174
- ]
22175
- }
22176
- ) : null
22177
- ]
22178
- }
22179
- ),
22180
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-labels", children: [
22181
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-title", children: [
22182
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-title-text", children: row.label }),
22183
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22184
- Badge,
22185
- {
22186
- variant: "secondary",
22187
- className: "hierarchy-timeline-pill",
22188
- semantic: row.badge,
22189
- children: row.badge
22190
- }
22191
- ),
22192
- row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22193
- "span",
22194
- {
22195
- className: "hierarchy-timeline-row-flag",
22196
- title: "Structured input detected for this call",
22197
- children: "Structured input"
22198
- }
22199
- ) : null,
22200
- row.hasHighlights && !row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22201
- "span",
22202
- {
22203
- className: "hierarchy-timeline-row-flag is-highlight",
22204
- title: "Trace insights available",
22205
- children: "Insight"
22206
- }
22207
- ) : null
22208
- ] }),
22209
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-meta", children: row.meta })
22210
- ] })
22211
- ] }),
22212
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-bars", children: [
22213
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22214
- "div",
22215
- {
22216
- className: "hierarchy-timeline-row-track",
22217
- "aria-hidden": "true",
22218
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-bar" })
22219
- }
22402
+ const rowStyle = {
22403
+ "--timeline-depth": String(row.depth),
22404
+ "--timeline-offset": String(
22405
+ model.durationMs > 0 ? row.offsetMs / model.durationMs : 0
22406
+ ),
22407
+ "--timeline-span": String(
22408
+ model.durationMs > 0 ? row.durationMs / model.durationMs : 1
22409
+ )
22410
+ };
22411
+ const rowContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react3.Fragment, { children: [
22412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-time", children: formatTimelineTimestamp(row.startedAt) }),
22413
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-branch", children: [
22414
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-gutter", "aria-hidden": "true", children: [
22415
+ row.ancestorContinuations.map((continues, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22416
+ "span",
22417
+ {
22418
+ className: cn(
22419
+ "hierarchy-timeline-row-ancestor",
22420
+ continues && "has-line"
22220
22421
  ),
22221
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-duration", children: formatElapsedLabel(row.durationMs) })
22222
- ] })
22223
- ] });
22224
- if (isTraceRow) {
22225
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22226
- "div",
22227
- {
22228
- role: "listitem",
22229
- title: buildHierarchyTimelineRowTooltip(row),
22230
- "aria-current": row.isActive || row.isDetailTrace ? "true" : void 0,
22231
- "data-hierarchy-row-id": row.id,
22232
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22233
- "button",
22422
+ style: {
22423
+ "--timeline-connector-index": String(index)
22424
+ }
22425
+ },
22426
+ `${row.id}-ancestor-${index}`
22427
+ )),
22428
+ row.depth > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22429
+ "span",
22430
+ {
22431
+ className: "hierarchy-timeline-row-connector",
22432
+ style: {
22433
+ "--timeline-connector-index": String(row.depth - 1)
22434
+ },
22435
+ children: [
22436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-connector-top" }),
22437
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-connector-elbow" }),
22438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22439
+ "span",
22234
22440
  {
22235
- type: "button",
22236
- className: rowClassName,
22237
- style: rowStyle,
22238
- onClick: () => onSelectRow(row.id),
22239
- children: rowContent
22441
+ className: cn(
22442
+ "hierarchy-timeline-row-connector-bottom",
22443
+ (row.hasVisibleChildren || !row.isLastSibling) && "has-line"
22444
+ )
22240
22445
  }
22241
22446
  )
22242
- },
22243
- row.id
22244
- );
22245
- }
22246
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22247
- "div",
22447
+ ]
22448
+ }
22449
+ ) : null
22450
+ ] }),
22451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-labels", children: [
22452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-title", children: [
22453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-title-text", children: row.label }),
22454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22455
+ Badge,
22456
+ {
22457
+ variant: "secondary",
22458
+ className: "hierarchy-timeline-pill",
22459
+ semantic: row.badge,
22460
+ children: row.badge
22461
+ }
22462
+ ),
22463
+ row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22464
+ "span",
22465
+ {
22466
+ className: "hierarchy-timeline-row-flag",
22467
+ title: "Structured input detected for this call",
22468
+ children: "Structured input"
22469
+ }
22470
+ ) : null,
22471
+ row.hasHighlights && !row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22472
+ "span",
22473
+ {
22474
+ className: "hierarchy-timeline-row-flag is-highlight",
22475
+ title: "Trace insights available",
22476
+ children: "Insight"
22477
+ }
22478
+ ) : null
22479
+ ] }),
22480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-meta", children: row.meta })
22481
+ ] })
22482
+ ] }),
22483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-bars", children: [
22484
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-track", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-bar" }) }),
22485
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-duration", children: formatElapsedLabel(row.durationMs) })
22486
+ ] })
22487
+ ] });
22488
+ if (isTraceRow) {
22489
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22490
+ "div",
22491
+ {
22492
+ role: "listitem",
22493
+ title: buildHierarchyTimelineRowTooltip(row),
22494
+ "aria-current": row.isActive || row.isDetailTrace ? "true" : void 0,
22495
+ "data-hierarchy-row-id": row.id,
22496
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22497
+ "button",
22248
22498
  {
22249
- role: "listitem",
22499
+ type: "button",
22250
22500
  className: rowClassName,
22251
22501
  style: rowStyle,
22252
- title: buildHierarchyTimelineRowTooltip(row),
22253
- "aria-current": row.isActive || row.isDetailTrace ? "true" : void 0,
22254
- "data-hierarchy-row-id": row.id,
22502
+ onClick: () => onSelectRow(row.id),
22255
22503
  children: rowContent
22256
- },
22257
- row.id
22258
- );
22259
- })
22260
- }
22261
- )
22262
- ] });
22504
+ }
22505
+ )
22506
+ },
22507
+ row.id
22508
+ );
22509
+ }
22510
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22511
+ "div",
22512
+ {
22513
+ role: "listitem",
22514
+ className: rowClassName,
22515
+ style: rowStyle,
22516
+ title: buildHierarchyTimelineRowTooltip(row),
22517
+ "aria-current": row.isActive || row.isDetailTrace ? "true" : void 0,
22518
+ "data-hierarchy-row-id": row.id,
22519
+ children: rowContent
22520
+ },
22521
+ row.id
22522
+ );
22523
+ });
22263
22524
  }
22264
22525
  function TraceDetailPanel({
22265
22526
  activeTab,
@@ -22271,10 +22532,8 @@ function TraceDetailPanel({
22271
22532
  onApplyTraceFilter,
22272
22533
  onBack,
22273
22534
  onNavigateHierarchyNode,
22274
- onSelectTimelineNode,
22275
22535
  onTabChange,
22276
- onToggleJsonMode,
22277
- timelineModel
22536
+ onToggleJsonMode
22278
22537
  }) {
22279
22538
  const traceDetailPrimaryRef = (0, import_react3.useRef)(null);
22280
22539
  const [showInlineContextRail, setShowInlineContextRail] = (0, import_react3.useState)(false);
@@ -22284,7 +22543,6 @@ function TraceDetailPanel({
22284
22543
  const detailSubtitle = formatTraceProviderSummary(detail ?? fallbackTrace);
22285
22544
  const detailCostUsd = detail ? getUsageCostUsd(detail.usage) : fallbackTrace?.costUsd ?? null;
22286
22545
  const detailCostLabel = formatUsdCost(detailCostUsd);
22287
- const hasSecondaryInspector = Boolean(detail && timelineModel);
22288
22546
  const detailFilterSource = detail ?? fallbackTrace;
22289
22547
  const canInlineContextRailTab = activeTab === "conversation" || activeTab === "request" || activeTab === "response";
22290
22548
  const visibleDetailTabs = canInlineContextRailTab && showInlineContextRail ? detailTabs.filter((tab) => tab.id !== "context") : detailTabs;
@@ -22386,77 +22644,59 @@ function TraceDetailPanel({
22386
22644
  onBack ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, { variant: "outline", onClick: onBack, children: "Show hierarchy" }) }) : null
22387
22645
  ] }),
22388
22646
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator, {}),
22389
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-body", children: detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22390
- "div",
22391
- {
22392
- className: cn(
22393
- "trace-detail-main",
22394
- hasSecondaryInspector && "has-secondary-inspector"
22395
- ),
22396
- children: [
22397
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-detail-primary", ref: traceDetailPrimaryRef, children: [
22398
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-detail-toolbar", children: [
22399
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-tabs-shell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tabs, { value: activeTab, onChange: onTabChange, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsList, { className: "detail-tabs", children: visibleDetailTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, { value: tab.id, children: tab.label }, tab.id)) }) }) }),
22400
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22401
- Button,
22647
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-body", children: detail ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-main", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-detail-primary", ref: traceDetailPrimaryRef, children: [
22648
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-detail-toolbar", children: [
22649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-tabs-shell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tabs, { value: activeTab, onChange: onTabChange, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsList, { className: "detail-tabs", children: visibleDetailTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabsTrigger, { value: tab.id, children: tab.label }, tab.id)) }) }) }),
22650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22651
+ Button,
22652
+ {
22653
+ variant: "outline",
22654
+ className: "trace-detail-json-toggle",
22655
+ onClick: () => onToggleJsonMode(activeTab),
22656
+ children: [
22657
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Boxes, { "data-icon": "inline-start" }),
22658
+ jsonMode === "formatted" ? "Raw JSON" : "Formatted"
22659
+ ]
22660
+ }
22661
+ )
22662
+ ] }),
22663
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator, {}),
22664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-pane", children: showContextRailInPrimary ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "detail-inline-context-layout", children: [
22665
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "detail-inline-context-main", children: renderTabContent(
22666
+ activeTab,
22667
+ detail,
22668
+ jsonMode,
22669
+ onApplyTagFilter,
22670
+ onApplyTraceFilter
22671
+ ) }),
22672
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22673
+ "aside",
22674
+ {
22675
+ className: "conversation-context-rail",
22676
+ "aria-label": "Context summary",
22677
+ children: [
22678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-context-rail-header", children: [
22679
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-context-rail-title", children: "Context" }),
22680
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-context-rail-copy", children: "Key trace metadata alongside the conversation." })
22681
+ ] }),
22682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22683
+ ContextInspectorView,
22402
22684
  {
22403
- variant: "outline",
22404
- className: "trace-detail-json-toggle",
22405
- onClick: () => onToggleJsonMode(activeTab),
22406
- children: [
22407
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Boxes, { "data-icon": "inline-start" }),
22408
- jsonMode === "formatted" ? "Raw JSON" : "Formatted"
22409
- ]
22685
+ detail,
22686
+ onApplyTagFilter
22410
22687
  }
22411
22688
  )
22412
- ] }),
22413
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator, {}),
22414
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-pane", children: showContextRailInPrimary ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "detail-inline-context-layout", children: [
22415
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "detail-inline-context-main", children: renderTabContent(
22416
- activeTab,
22417
- detail,
22418
- jsonMode,
22419
- onApplyTagFilter,
22420
- onApplyTraceFilter
22421
- ) }),
22422
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22423
- "aside",
22424
- {
22425
- className: "conversation-context-rail",
22426
- "aria-label": "Context summary",
22427
- children: [
22428
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-context-rail-header", children: [
22429
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-context-rail-title", children: "Context" }),
22430
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-context-rail-copy", children: "Key trace metadata alongside the conversation." })
22431
- ] }),
22432
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22433
- ContextInspectorView,
22434
- {
22435
- detail,
22436
- onApplyTagFilter
22437
- }
22438
- )
22439
- ]
22440
- }
22441
- )
22442
- ] }) : renderTabContent(
22443
- activeTab,
22444
- detail,
22445
- jsonMode,
22446
- onApplyTagFilter,
22447
- onApplyTraceFilter
22448
- ) }) })
22449
- ] }),
22450
- timelineModel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", { className: "trace-detail-secondary", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22451
- HierarchyTimelineOverview,
22452
- {
22453
- model: timelineModel,
22454
- onSelectRow: onSelectTimelineNode
22455
- }
22456
- ) }) : null
22457
- ]
22458
- }
22459
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-empty", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22689
+ ]
22690
+ }
22691
+ )
22692
+ ] }) : renderTabContent(
22693
+ activeTab,
22694
+ detail,
22695
+ jsonMode,
22696
+ onApplyTagFilter,
22697
+ onApplyTraceFilter
22698
+ ) }) })
22699
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-empty", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22460
22700
  EmptyState,
22461
22701
  {
22462
22702
  icon: ArrowUpRight,
@@ -23528,52 +23768,6 @@ function buildDetailTabs(detail) {
23528
23768
  }
23529
23769
  return tabs;
23530
23770
  }
23531
- function findNodeById(nodes, id) {
23532
- for (const node of nodes) {
23533
- if (node.id === id) {
23534
- return node;
23535
- }
23536
- const child = findNodeById(node.children, id);
23537
- if (child) {
23538
- return child;
23539
- }
23540
- }
23541
- return null;
23542
- }
23543
- function findNodePath(nodes, id, trail = []) {
23544
- for (const node of nodes) {
23545
- const nextTrail = [...trail, node];
23546
- if (node.id === id) {
23547
- return nextTrail;
23548
- }
23549
- const childTrail = findNodePath(node.children, id, nextTrail);
23550
- if (childTrail.length) {
23551
- return childTrail;
23552
- }
23553
- }
23554
- return [];
23555
- }
23556
- function getNewestTraceId(node) {
23557
- if (!node?.traceIds.length) {
23558
- return null;
23559
- }
23560
- return node.traceIds[0] || null;
23561
- }
23562
- function getDefaultExpandedNodeIds(nodes) {
23563
- const expanded = /* @__PURE__ */ new Set();
23564
- const visit = (node) => {
23565
- if (node.children.length && (node.type === "session" || node.type === "actor")) {
23566
- expanded.add(node.id);
23567
- }
23568
- for (const child of node.children) {
23569
- visit(child);
23570
- }
23571
- };
23572
- for (const node of nodes) {
23573
- visit(node);
23574
- }
23575
- return expanded;
23576
- }
23577
23771
  function getHierarchyNodeCopy(node, traceById) {
23578
23772
  switch (node.type) {
23579
23773
  case "session":
@@ -23710,40 +23904,6 @@ function formatTraceProviderSummary(trace) {
23710
23904
  }
23711
23905
  return formatList([trace.provider, trace.model]);
23712
23906
  }
23713
- function groupTracesForNav(items) {
23714
- const groups = /* @__PURE__ */ new Map();
23715
- const uniqueSessions = new Set(items.map((item) => item.hierarchy.sessionId));
23716
- const groupMode = uniqueSessions.size > 1 ? "session" : "kind";
23717
- for (const item of items) {
23718
- const key = groupMode === "session" ? `session:${item.hierarchy.sessionId || "unknown-session"}` : `kind:${item.kind}:${item.hierarchy.guardrailPhase || item.hierarchy.stage || item.mode}`;
23719
- const group = groups.get(key) ?? [];
23720
- group.push(item);
23721
- groups.set(key, group);
23722
- }
23723
- return [...groups.entries()].map(([groupKey, traces]) => {
23724
- const latestStartedAt = traces[0]?.startedAt ?? null;
23725
- return {
23726
- costUsd: sumTraceCosts(traces),
23727
- id: `trace-group:${groupKey}`,
23728
- isAggregateCost: traces.length > 1,
23729
- items: traces,
23730
- label: groupMode === "session" ? `Session ${shortId2(traces[0]?.hierarchy.sessionId || "unknown")}` : getTraceActorLabel(traces[0]),
23731
- meta: formatList([
23732
- latestStartedAt ? formatCompactTimestamp2(latestStartedAt) : null,
23733
- formatCountLabel(traces.length, "trace")
23734
- ]) || "No metadata"
23735
- };
23736
- });
23737
- }
23738
- function sumTraceCosts(items) {
23739
- const costs = items.map((item) => item.costUsd).filter(
23740
- (value) => value !== null && Number.isFinite(value)
23741
- );
23742
- if (!costs.length) {
23743
- return null;
23744
- }
23745
- return roundCostUsd2(costs.reduce((sum, value) => sum + value, 0));
23746
- }
23747
23907
  function getTraceActorLabel(trace) {
23748
23908
  if (trace.kind === "guardrail") {
23749
23909
  return `${capitalize(trace.hierarchy.guardrailPhase || "guardrail")} guardrail`;
@@ -24287,9 +24447,6 @@ async function copyText(value) {
24287
24447
  return false;
24288
24448
  }
24289
24449
  }
24290
- function toSessionNodeId(sessionId) {
24291
- return `session:${sessionId}`;
24292
- }
24293
24450
  function toTraceNodeId(traceId) {
24294
24451
  return `trace:${traceId}`;
24295
24452
  }
@@ -24784,12 +24941,6 @@ function getSemanticBadgeValue(value) {
24784
24941
  return null;
24785
24942
  }
24786
24943
  }
24787
- function formatCompactTimestamp2(value) {
24788
- return new Date(value).toLocaleTimeString([], {
24789
- hour: "2-digit",
24790
- minute: "2-digit"
24791
- });
24792
- }
24793
24944
  function formatTimelineTimestamp(value) {
24794
24945
  return new Date(value).toLocaleTimeString([], {
24795
24946
  hour: "2-digit",
@@ -24803,6 +24954,14 @@ function getMaxDurationMs(items) {
24803
24954
  );
24804
24955
  return durations.length ? Math.max(...durations) : 1;
24805
24956
  }
24957
+ function getElapsedScale(durationMs, maxDurationMs) {
24958
+ if (durationMs === null || durationMs <= 0 || !Number.isFinite(durationMs)) {
24959
+ return 0.16;
24960
+ }
24961
+ const safeMax = Math.max(maxDurationMs, durationMs, 1);
24962
+ const ratio = Math.log1p(durationMs) / Math.log1p(safeMax);
24963
+ return Math.max(0.14, Math.min(1, ratio));
24964
+ }
24806
24965
  function formatElapsedLabel(durationMs) {
24807
24966
  if (durationMs === null || !Number.isFinite(durationMs)) {
24808
24967
  return "Running";
@@ -24993,7 +25152,7 @@ function parseEvent(data) {
24993
25152
  return null;
24994
25153
  }
24995
25154
  }
24996
- var import_react3, import_jsx_runtime, MESSAGE_COLLAPSE_CHAR_LIMIT, MESSAGE_COLLAPSE_LINE_LIMIT, MESSAGE_COLLAPSE_HEIGHT_PROSE, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED, MESSAGE_COLLAPSE_CHAR_LIMIT_SYSTEM, MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED_SYSTEM, TIMELINE_AXIS_STOPS, COMPACT_TIMELINE_AXIS_STOPS, EMPTY_TRACE_INSIGHTS, MarkdownBlock, STATUS_OPTIONS, KIND_OPTIONS, INITIAL_FILTERS;
25155
+ var import_react3, import_jsx_runtime, MESSAGE_COLLAPSE_CHAR_LIMIT, MESSAGE_COLLAPSE_LINE_LIMIT, MESSAGE_COLLAPSE_HEIGHT_PROSE, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED, MESSAGE_COLLAPSE_CHAR_LIMIT_SYSTEM, MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM, MESSAGE_COLLAPSE_HEIGHT_STRUCTURED_SYSTEM, EMPTY_TRACE_INSIGHTS, MarkdownBlock, STATUS_OPTIONS, KIND_OPTIONS, INITIAL_FILTERS;
24997
25156
  var init_app = __esm({
24998
25157
  "client/src/app.tsx"() {
24999
25158
  init_lucide_react();
@@ -25011,8 +25170,6 @@ var init_app = __esm({
25011
25170
  MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM = 5;
25012
25171
  MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM = "9rem";
25013
25172
  MESSAGE_COLLAPSE_HEIGHT_STRUCTURED_SYSTEM = "9rem";
25014
- TIMELINE_AXIS_STOPS = [0, 0.25, 0.5, 0.75, 1];
25015
- COMPACT_TIMELINE_AXIS_STOPS = [0, 0.5, 1];
25016
25173
  EMPTY_TRACE_INSIGHTS = {
25017
25174
  highlights: [],
25018
25175
  structuredInputs: []