@mtharrison/loupe 1.1.1 → 1.2.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);
@@ -21373,75 +21446,61 @@ function App() {
21373
21446
  () => getMaxDurationMs(traceItems),
21374
21447
  [traceItems]
21375
21448
  );
21376
- const traceGroups = (0, import_react3.useMemo)(
21377
- () => groupTracesForNav(traceItems),
21378
- [traceItems]
21379
- );
21380
21449
  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]
21450
+ () => sortSessionNodesForNav(
21451
+ data.hierarchy.rootNodes.filter((node) => node.type === "session"),
21452
+ traceById
21453
+ ),
21454
+ [data.hierarchy.rootNodes, traceById]
21387
21455
  );
21388
21456
  const sessionNavItems = (0, import_react3.useMemo)(
21389
21457
  () => deriveSessionNavItems(sessionNodes, traceById),
21390
21458
  [sessionNodes, traceById]
21391
21459
  );
21460
+ const sessionNavById = (0, import_react3.useMemo)(
21461
+ () => new Map(sessionNavItems.map((item) => [item.id, item])),
21462
+ [sessionNavItems]
21463
+ );
21392
21464
  const hasActiveFilters = Boolean(
21393
21465
  filters.search || filters.status || filters.kind || filters.tags
21394
21466
  );
21395
- const filteredSessionCount = sessionNavItems.length;
21467
+ const filteredSessionCount = sessionNodes.length;
21396
21468
  const hasRecordedSessions = allSessionCount > 0;
21397
21469
  const showFilteredSessionEmptyState = hasActiveFilters && hasRecordedSessions && filteredSessionCount === 0;
21470
+ const selectedNodePath = (0, import_react3.useMemo)(
21471
+ () => selectedNodeId ? findSessionNodePath(sessionNodes, selectedNodeId) : [],
21472
+ [sessionNodes, selectedNodeId]
21473
+ );
21474
+ const selectedTracePath = (0, import_react3.useMemo)(
21475
+ () => selectedTraceId ? findSessionNodePath(sessionNodes, toTraceNodeId(selectedTraceId)) : [],
21476
+ [sessionNodes, selectedTraceId]
21477
+ );
21478
+ const selectedPathIds = (0, import_react3.useMemo)(
21479
+ () => new Set(selectedNodePath.map((node) => node.id)),
21480
+ [selectedNodePath]
21481
+ );
21398
21482
  const selectedSessionNode = (0, import_react3.useMemo)(
21399
- () => selectedSessionId ? sessionNodeById.get(selectedSessionId) ?? null : (sessionNavItems[0] && sessionNodeById.get(sessionNavItems[0].id)) ?? null,
21400
- [selectedSessionId, sessionNavItems, sessionNodeById]
21483
+ () => (selectedNodePath[0]?.type === "session" ? selectedNodePath[0] : null) ?? (selectedTracePath[0]?.type === "session" ? selectedTracePath[0] : null) ?? sessionNodes[0] ?? null,
21484
+ [selectedNodePath, selectedTracePath, sessionNodes]
21401
21485
  );
21402
21486
  const selectedTraceSummary = (0, import_react3.useMemo)(
21403
21487
  () => selectedTraceId ? traceById.get(selectedTraceId) ?? null : null,
21404
21488
  [selectedTraceId, traceById]
21405
21489
  );
21406
21490
  (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
- }
21491
+ const nextSelection = resolveSessionTreeSelection(
21492
+ sessionNodes,
21493
+ selectedNodeId,
21494
+ selectedTraceId
21495
+ );
21496
+ if (nextSelection.selectedNodeId === selectedNodeId && nextSelection.selectedTraceId === selectedTraceId) {
21430
21497
  return;
21431
21498
  }
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
- ]);
21499
+ (0, import_react3.startTransition)(() => {
21500
+ setSelectedNodeId(nextSelection.selectedNodeId);
21501
+ setSelectedTraceId(nextSelection.selectedTraceId);
21502
+ });
21503
+ }, [selectedNodeId, selectedTraceId, sessionNodes]);
21445
21504
  const loadDetail = (0, import_react3.useEffectEvent)(async (traceId) => {
21446
21505
  const requestId = detailRequestRef.current + 1;
21447
21506
  detailRequestRef.current = requestId;
@@ -21468,43 +21527,13 @@ function App() {
21468
21527
  void loadDetail(selectedTraceId);
21469
21528
  }, [selectedTraceId]);
21470
21529
  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
21530
+ () => getDefaultExpandedSessionTreeNodeIds(
21531
+ sessionNodes,
21532
+ selectedSessionNode?.id ?? null,
21533
+ selectedNodeId
21485
21534
  ),
21486
- [
21487
- selectedNodeId,
21488
- selectedNodePath,
21489
- selectedSessionNode,
21490
- selectedTraceId,
21491
- traceById
21492
- ]
21535
+ [selectedNodeId, selectedSessionNode, sessionNodes]
21493
21536
  );
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
21537
  const activeTabJsonMode = tabModes[detailTab] ?? "formatted";
21509
21538
  const activeTagFilterCount = countTagFilters(filters.tags);
21510
21539
  const onFilterChange = (key, value) => {
@@ -21543,24 +21572,13 @@ function App() {
21543
21572
  }));
21544
21573
  });
21545
21574
  };
21546
- const toggleTraceGroupCollapse = (groupId) => {
21547
- (0, import_react3.startTransition)(() => {
21548
- setCollapsedTraceGroups((current) => ({
21549
- ...current,
21550
- [groupId]: !current[groupId]
21551
- }));
21552
- });
21553
- };
21554
21575
  const resetFilters = () => {
21555
- const nextSessionNode = (sessionNavItems[0] && sessionNodeById.get(sessionNavItems[0].id)) ?? null;
21556
21576
  (0, import_react3.startTransition)(() => {
21557
21577
  setFilters(INITIAL_FILTERS);
21558
21578
  setShowAdvancedFilters(false);
21559
- setSelectedSessionId(nextSessionNode?.id ?? null);
21560
- setSelectedNodeId(nextSessionNode?.id ?? null);
21561
- setSelectedTraceId(
21562
- navMode === "traces" ? traceItems[0]?.id ?? null : getNewestTraceId(nextSessionNode)
21563
- );
21579
+ setSelectedNodeId(null);
21580
+ setSelectedTraceId(null);
21581
+ setDetailTab("conversation");
21564
21582
  });
21565
21583
  };
21566
21584
  const clearTraces = async () => {
@@ -21594,78 +21612,21 @@ function App() {
21594
21612
  () => setIsSessionsPanelCollapsed((current) => !current)
21595
21613
  );
21596
21614
  };
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
21615
  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;
21616
+ const nextTraceId = node.type === "trace" ? node.meta.traceId ?? node.traceIds[0] ?? null : getNewestTraceIdForNode(node);
21642
21617
  (0, import_react3.startTransition)(() => {
21643
- setNavMode("sessions");
21644
- setSelectedSessionId(nextSessionId ?? null);
21645
- setSelectedNodeId(nextSelectedNodeId);
21618
+ setSelectedNodeId(node.id);
21646
21619
  setSelectedTraceId(nextTraceId);
21647
21620
  setDetailTab("conversation");
21648
21621
  });
21649
21622
  };
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
21623
  const navigateToHierarchyNode = (nodeId) => {
21659
- const node = findNodeById(data.hierarchy.rootNodes, nodeId);
21624
+ const node = findSessionNodeById(sessionNodes, nodeId);
21660
21625
  if (!node) {
21661
21626
  return;
21662
21627
  }
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);
21628
+ const nextTraceId = selectedTraceId && node.traceIds.includes(selectedTraceId) ? selectedTraceId : getNewestTraceIdForNode(node);
21666
21629
  (0, import_react3.startTransition)(() => {
21667
- setNavMode("sessions");
21668
- setSelectedSessionId(nextSessionId ?? null);
21669
21630
  setSelectedNodeId(nodeId);
21670
21631
  if (nextTraceId !== selectedTraceId) {
21671
21632
  setSelectedTraceId(nextTraceId);
@@ -21674,17 +21635,6 @@ function App() {
21674
21635
  };
21675
21636
  const detailTabs = buildDetailTabs(detail);
21676
21637
  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
21638
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "app-shell", children: [
21689
21639
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BackgroundGlow, {}),
21690
21640
  /* @__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 +21763,45 @@ function App() {
21813
21763
  isSessionsPanelCollapsed && "is-sidebar-collapsed"
21814
21764
  ),
21815
21765
  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)(
21766
+ /* @__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)(
21767
+ SessionTreeNavigator,
21768
+ {
21769
+ defaultExpandedNodeIds,
21770
+ expandedNodeOverrides,
21771
+ hasActiveFilters,
21772
+ items: sessionNavItems,
21773
+ maxDurationMs: navigatorMaxDurationMs,
21774
+ nodes: sessionNodes,
21775
+ onSelect: handleHierarchySelect,
21776
+ onToggle: toggleNodeExpansion,
21777
+ selectedNodeId,
21778
+ selectedPathIds,
21779
+ selectedTraceId,
21780
+ sessionNavById,
21781
+ totalCount: allSessionCount,
21782
+ traceById
21783
+ }
21784
+ ) : /* @__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: [
21785
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21848
21786
  EmptyState,
21849
21787
  {
21850
- icon: Network,
21851
- title: "No sessions yet",
21852
- description: "Trigger any traced LLM call and the session timeline will appear here."
21788
+ icon: Funnel,
21789
+ title: "No sessions match the current filters",
21790
+ description: "Try adjusting the search, status, kind, or tag filters to broaden the result set."
21853
21791
  }
21854
- ) })
21855
- ] }) }),
21792
+ ),
21793
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, { variant: "outline", onClick: resetFilters, children: [
21794
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { "data-icon": "inline-start" }),
21795
+ "Clear filters"
21796
+ ] })
21797
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21798
+ EmptyState,
21799
+ {
21800
+ icon: Network,
21801
+ title: "No sessions yet",
21802
+ description: "Trigger any traced LLM call and the session tree will appear here."
21803
+ }
21804
+ ) }) }) }),
21856
21805
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { className: "timeline-card content-card inspector-card", children: selectedTraceId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21857
21806
  TraceDetailPanel,
21858
21807
  {
@@ -21865,20 +21814,18 @@ function App() {
21865
21814
  onApplyTraceFilter: applyTraceFilter,
21866
21815
  onNavigateHierarchyNode: navigateToHierarchyNode,
21867
21816
  onTabChange: (value) => setDetailTab(value),
21868
- onSelectTimelineNode: handleTimelineSelect,
21869
21817
  onToggleJsonMode: (tabId) => (0, import_react3.startTransition)(() => {
21870
21818
  setTabModes((current) => ({
21871
21819
  ...current,
21872
21820
  [tabId]: (current[tabId] ?? "formatted") === "formatted" ? "raw" : "formatted"
21873
21821
  }));
21874
- }),
21875
- timelineModel: null
21822
+ })
21876
21823
  }
21877
21824
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, { className: "content-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21878
21825
  EmptyState,
21879
21826
  {
21880
21827
  icon: ArrowUpRight,
21881
- title: "Select a trace from the session timeline",
21828
+ title: "Select a trace from the session tree",
21882
21829
  description: "Choose any call on the left to inspect the full request, response, context, and stream details."
21883
21830
  }
21884
21831
  ) }) })
@@ -21931,63 +21878,51 @@ function ThemeSwitcher({
21931
21878
  )
21932
21879
  ] });
21933
21880
  }
21934
- function SessionNavList({
21881
+ function SessionTreeNavigator({
21882
+ defaultExpandedNodeIds,
21883
+ expandedNodeOverrides,
21935
21884
  hasActiveFilters,
21936
21885
  items,
21937
- onChange,
21938
- selectedId,
21939
- totalCount
21886
+ maxDurationMs,
21887
+ nodes,
21888
+ onSelect,
21889
+ onToggle,
21890
+ selectedNodeId,
21891
+ selectedPathIds,
21892
+ selectedTraceId,
21893
+ sessionNavById,
21894
+ totalCount,
21895
+ traceById
21940
21896
  }) {
21941
21897
  const filteredCount = items.length;
21942
21898
  const countLabel = hasActiveFilters && totalCount > filteredCount ? `${filteredCount} of ${totalCount} sessions` : formatCountLabel(
21943
21899
  hasActiveFilters ? filteredCount : Math.max(filteredCount, totalCount),
21944
21900
  "session"
21945
21901
  );
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
21902
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-section", children: [
21903
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-header", children: [
21904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "session-tree-title-row", children: [
21905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-title", children: "Sessions" }),
21906
+ hasActiveFilters ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", className: "session-tree-filter-badge", children: "Filtered" }) : null
21951
21907
  ] }),
21952
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-nav-meta", children: countLabel })
21908
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-meta", children: countLabel })
21953
21909
  ] }),
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
- }) })
21910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21911
+ HierarchyTree,
21912
+ {
21913
+ defaultExpandedNodeIds,
21914
+ expandedNodeOverrides,
21915
+ maxDurationMs,
21916
+ nodes,
21917
+ onSelect,
21918
+ onToggle,
21919
+ selectedNodeId,
21920
+ selectedPathIds,
21921
+ selectedTraceId,
21922
+ sessionNavById,
21923
+ traceById
21924
+ }
21925
+ ) })
21991
21926
  ] });
21992
21927
  }
21993
21928
  function getSessionNavBadge(item) {
@@ -22009,6 +21944,374 @@ function BackgroundGlow() {
22009
21944
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid-noise" })
22010
21945
  ] });
22011
21946
  }
21947
+ function HierarchyTree({
21948
+ defaultExpandedNodeIds,
21949
+ expandedNodeOverrides,
21950
+ maxDurationMs,
21951
+ nodes,
21952
+ onSelect,
21953
+ onToggle,
21954
+ selectedNodeId,
21955
+ selectedPathIds,
21956
+ selectedTraceId,
21957
+ sessionNavById,
21958
+ traceById
21959
+ }) {
21960
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-root", children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
21961
+ HierarchyTreeNode,
21962
+ {
21963
+ defaultExpandedNodeIds,
21964
+ depth: 0,
21965
+ expandedNodeOverrides,
21966
+ maxDurationMs,
21967
+ node,
21968
+ onSelect,
21969
+ onToggle,
21970
+ selectedNodeId,
21971
+ selectedPathIds,
21972
+ selectedTraceId,
21973
+ sessionNavById,
21974
+ traceById
21975
+ },
21976
+ node.id
21977
+ )) });
21978
+ }
21979
+ function HierarchyTreeNode({
21980
+ defaultExpandedNodeIds,
21981
+ depth,
21982
+ expandedNodeOverrides,
21983
+ maxDurationMs,
21984
+ node,
21985
+ onSelect,
21986
+ onToggle,
21987
+ selectedNodeId,
21988
+ selectedPathIds,
21989
+ selectedTraceId,
21990
+ sessionNavById,
21991
+ traceById
21992
+ }) {
21993
+ const isExpandable = node.children.length > 0;
21994
+ const isForcedExpanded = isExpandable && selectedPathIds.has(node.id);
21995
+ const expandedOverride = expandedNodeOverrides[node.id];
21996
+ const isExpanded = isExpandable && (expandedOverride ?? (isForcedExpanded || defaultExpandedNodeIds.has(node.id)));
21997
+ const isInPath = selectedPathIds.has(node.id);
21998
+ const nodeCopy = getHierarchyNodeCopy(node, traceById);
21999
+ const trace = node.meta.traceId ? traceById.get(node.meta.traceId) ?? null : null;
22000
+ const sessionNavItem = node.type === "session" ? sessionNavById.get(node.id) ?? null : null;
22001
+ if (node.type === "trace" && trace) {
22002
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22003
+ TraceHierarchyLeaf,
22004
+ {
22005
+ depth,
22006
+ maxDurationMs,
22007
+ node,
22008
+ nodeCopy,
22009
+ onSelect,
22010
+ inPath: isInPath,
22011
+ selected: selectedNodeId === node.id,
22012
+ selectedTrace: selectedTraceId === trace.id,
22013
+ trace
22014
+ }
22015
+ );
22016
+ }
22017
+ if (sessionNavItem) {
22018
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22019
+ SessionHierarchyBranch,
22020
+ {
22021
+ depth,
22022
+ defaultExpandedNodeIds,
22023
+ expandedNodeOverrides,
22024
+ isExpanded,
22025
+ item: sessionNavItem,
22026
+ maxDurationMs,
22027
+ node,
22028
+ onSelect,
22029
+ onToggle,
22030
+ selected: selectedNodeId === node.id,
22031
+ selectedNodeId,
22032
+ selectedPathIds,
22033
+ selectedTraceId,
22034
+ sessionNavById,
22035
+ traceById
22036
+ }
22037
+ );
22038
+ }
22039
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22040
+ "div",
22041
+ {
22042
+ className: "tree-node-wrap",
22043
+ style: { "--depth": String(depth) },
22044
+ children: [
22045
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22046
+ "div",
22047
+ {
22048
+ className: clsx_default(
22049
+ "tree-node-card",
22050
+ selectedNodeId === node.id && "is-active",
22051
+ isInPath && "is-in-path",
22052
+ node.type === "trace" && "is-trace"
22053
+ ),
22054
+ children: [
22055
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22056
+ "button",
22057
+ {
22058
+ type: "button",
22059
+ className: clsx_default("tree-node-toggle", !isExpandable && "is-static"),
22060
+ disabled: !isExpandable,
22061
+ onClick: () => {
22062
+ if (isExpandable) {
22063
+ onToggle(node.id);
22064
+ }
22065
+ },
22066
+ "aria-label": isExpandable ? `${isExpanded ? "Collapse" : "Expand"} ${nodeCopy.label}` : void 0,
22067
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: clsx_default(isExpanded && "is-open") })
22068
+ }
22069
+ ),
22070
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22071
+ "button",
22072
+ {
22073
+ type: "button",
22074
+ className: "tree-node-select",
22075
+ onClick: () => onSelect(node),
22076
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
22077
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-heading", children: [
22078
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
22079
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22080
+ Badge,
22081
+ {
22082
+ variant: "secondary",
22083
+ className: "tree-node-inline-badge",
22084
+ semantic: nodeCopy.badge,
22085
+ children: nodeCopy.badge
22086
+ }
22087
+ )
22088
+ ] }),
22089
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: nodeCopy.meta })
22090
+ ] })
22091
+ }
22092
+ )
22093
+ ]
22094
+ }
22095
+ ),
22096
+ isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22097
+ HierarchyTreeNode,
22098
+ {
22099
+ defaultExpandedNodeIds,
22100
+ depth: depth + 1,
22101
+ expandedNodeOverrides,
22102
+ maxDurationMs,
22103
+ node: child,
22104
+ onSelect,
22105
+ onToggle,
22106
+ selectedNodeId,
22107
+ selectedPathIds,
22108
+ selectedTraceId,
22109
+ sessionNavById,
22110
+ traceById
22111
+ },
22112
+ child.id
22113
+ )) }) : null
22114
+ ]
22115
+ }
22116
+ );
22117
+ }
22118
+ function SessionHierarchyBranch({
22119
+ defaultExpandedNodeIds,
22120
+ depth,
22121
+ expandedNodeOverrides,
22122
+ isExpanded,
22123
+ item,
22124
+ maxDurationMs,
22125
+ node,
22126
+ onSelect,
22127
+ onToggle,
22128
+ selected,
22129
+ selectedNodeId,
22130
+ selectedPathIds,
22131
+ selectedTraceId,
22132
+ sessionNavById,
22133
+ traceById
22134
+ }) {
22135
+ const detailLabel = formatList([
22136
+ formatCountLabel(item.callCount, "call"),
22137
+ formatUsdCost(item.costUsd)
22138
+ ]) || formatCountLabel(item.callCount, "call");
22139
+ const sessionSelectedPath = selectedNodeId ? findSessionNodePath([node], selectedNodeId) : [];
22140
+ const sessionTimelineModel = buildHierarchyTimelineModel(
22141
+ node,
22142
+ traceById,
22143
+ selectedNodeId,
22144
+ sessionSelectedPath,
22145
+ selectedTraceId
22146
+ );
22147
+ const sessionTimelineRow = sessionTimelineModel?.rows[0] ?? null;
22148
+ const showEmbeddedTimeline = isExpanded && Boolean(sessionTimelineModel?.rows.length);
22149
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22150
+ "div",
22151
+ {
22152
+ className: "tree-node-wrap",
22153
+ style: { "--depth": String(depth) },
22154
+ children: [
22155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22156
+ "div",
22157
+ {
22158
+ className: clsx_default(
22159
+ "tree-node-card tree-session-card",
22160
+ selected && "is-active",
22161
+ selectedPathIds.has(node.id) && "is-in-path"
22162
+ ),
22163
+ children: [
22164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22165
+ "button",
22166
+ {
22167
+ type: "button",
22168
+ className: clsx_default(
22169
+ "tree-node-toggle",
22170
+ !node.children.length && "is-static"
22171
+ ),
22172
+ disabled: !node.children.length,
22173
+ onClick: () => {
22174
+ if (node.children.length) {
22175
+ onToggle(node.id);
22176
+ }
22177
+ },
22178
+ "aria-label": node.children.length ? `${isExpanded ? "Collapse" : "Expand"} ${item.primaryLabel}` : void 0,
22179
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: clsx_default(isExpanded && "is-open") })
22180
+ }
22181
+ ),
22182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22183
+ "button",
22184
+ {
22185
+ type: "button",
22186
+ className: "tree-node-select session-tree-select",
22187
+ onClick: () => onSelect(node),
22188
+ children: [
22189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy session-tree-copy", children: [
22190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-card-header", children: [
22191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-card-title", title: item.primaryLabel, children: item.primaryLabel }),
22192
+ item.latestTimestamp ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-card-time", children: item.latestTimestamp }) : null
22193
+ ] }),
22194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: detailLabel }),
22195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-card-footer", children: [
22196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-card-id", children: item.shortSessionId }),
22197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-card-badges", children: [
22198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { variant: "outline", children: "Session" }),
22199
+ getSessionNavBadge(item)
22200
+ ] })
22201
+ ] })
22202
+ ] }),
22203
+ sessionTimelineModel && sessionTimelineRow ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22204
+ SessionTreeTimelineBar,
22205
+ {
22206
+ model: sessionTimelineModel,
22207
+ row: sessionTimelineRow
22208
+ }
22209
+ ) : null
22210
+ ]
22211
+ }
22212
+ )
22213
+ ]
22214
+ }
22215
+ ),
22216
+ showEmbeddedTimeline && sessionTimelineModel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "session-tree-timeline-shell", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22217
+ "div",
22218
+ {
22219
+ className: "session-tree-timeline-list",
22220
+ role: "list",
22221
+ "aria-label": "Session timeline",
22222
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22223
+ HierarchyTimelineRows,
22224
+ {
22225
+ className: "is-embedded",
22226
+ model: sessionTimelineModel,
22227
+ onSelectRow: (nodeId) => {
22228
+ const selectedTimelineNode = findSessionNodeById([node], nodeId);
22229
+ if (selectedTimelineNode) {
22230
+ onSelect(selectedTimelineNode);
22231
+ }
22232
+ },
22233
+ rows: sessionTimelineModel.rows.slice(1)
22234
+ }
22235
+ )
22236
+ }
22237
+ ) }) : isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tree-node-children", children: node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22238
+ HierarchyTreeNode,
22239
+ {
22240
+ defaultExpandedNodeIds,
22241
+ depth: depth + 1,
22242
+ expandedNodeOverrides,
22243
+ maxDurationMs,
22244
+ node: child,
22245
+ onSelect,
22246
+ onToggle,
22247
+ selectedNodeId,
22248
+ selectedPathIds,
22249
+ selectedTraceId,
22250
+ sessionNavById,
22251
+ traceById
22252
+ },
22253
+ child.id
22254
+ )) }) : null
22255
+ ]
22256
+ }
22257
+ );
22258
+ }
22259
+ function TraceHierarchyLeaf({
22260
+ depth,
22261
+ inPath,
22262
+ maxDurationMs,
22263
+ node,
22264
+ nodeCopy,
22265
+ onSelect,
22266
+ selected,
22267
+ selectedTrace,
22268
+ trace
22269
+ }) {
22270
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22271
+ "div",
22272
+ {
22273
+ className: "tree-node-wrap tree-trace-wrap",
22274
+ style: { "--depth": String(depth) },
22275
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22276
+ "div",
22277
+ {
22278
+ className: clsx_default(
22279
+ "tree-node-card is-trace",
22280
+ inPath && "is-in-path",
22281
+ selected && "is-active",
22282
+ selectedTrace && "is-detail-trace"
22283
+ ),
22284
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22285
+ "button",
22286
+ {
22287
+ type: "button",
22288
+ className: "tree-node-select tree-trace-select",
22289
+ onClick: () => onSelect(node),
22290
+ children: [
22291
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tree-node-copy", children: [
22292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-nav-kicker", children: getTraceActorLabel(trace) }),
22293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-label", children: nodeCopy.label }),
22294
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tree-node-meta", children: formatList([
22295
+ formatTimelineTimestamp(trace.startedAt),
22296
+ nodeCopy.meta
22297
+ ]) })
22298
+ ] }),
22299
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22300
+ TraceElapsedBar,
22301
+ {
22302
+ compact: true,
22303
+ durationMs: trace.durationMs,
22304
+ maxDurationMs
22305
+ }
22306
+ )
22307
+ ]
22308
+ }
22309
+ )
22310
+ }
22311
+ )
22312
+ }
22313
+ );
22314
+ }
22012
22315
  function StatusBadge({
22013
22316
  status,
22014
22317
  onClick
@@ -22031,235 +22334,191 @@ function TraceMetricPill({
22031
22334
  }
22032
22335
  );
22033
22336
  }
22034
- function HierarchyTimelineOverview({
22035
- axisStops = TIMELINE_AXIS_STOPS,
22036
- model,
22037
- onSelectRow
22337
+ function TraceElapsedBar({
22338
+ compact = false,
22339
+ durationMs,
22340
+ maxDurationMs
22038
22341
  }) {
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;
22342
+ const durationLabel = formatElapsedLabel(durationMs);
22343
+ const scale = getElapsedScale(durationMs, maxDurationMs);
22344
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22345
+ "div",
22346
+ {
22347
+ className: cn(
22348
+ "trace-elapsed-bar",
22349
+ compact && "is-compact",
22350
+ durationMs === null && "is-pending"
22351
+ ),
22352
+ style: { "--elapsed-scale": String(scale) },
22353
+ children: [
22354
+ /* @__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" }) }),
22355
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "trace-elapsed-label", children: durationLabel })
22356
+ ]
22061
22357
  }
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;
22358
+ );
22359
+ }
22360
+ function SessionTreeTimelineBar({
22361
+ model,
22362
+ row
22363
+ }) {
22364
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22365
+ "span",
22366
+ {
22367
+ className: "session-tree-timeline",
22368
+ style: {
22369
+ "--session-tree-offset": model.durationMs > 0 ? String(row.offsetMs / model.durationMs) : "0",
22370
+ "--session-tree-span": model.durationMs > 0 ? String(row.durationMs / model.durationMs) : "1"
22371
+ },
22372
+ children: [
22373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "session-tree-timeline-meta", children: [
22374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-timeline-start", children: formatTimelineTimestamp(row.startedAt) }),
22375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "session-tree-timeline-duration", children: formatElapsedLabel(row.durationMs) })
22376
+ ] }),
22377
+ /* @__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" }) })
22378
+ ]
22068
22379
  }
22069
- const contextOffset = Math.max(
22070
- target.offsetHeight * 1.5,
22071
- list.clientHeight * 0.35
22380
+ );
22381
+ }
22382
+ function HierarchyTimelineRows({
22383
+ className,
22384
+ model,
22385
+ onSelectRow,
22386
+ rows
22387
+ }) {
22388
+ return rows.map((row) => {
22389
+ const isTraceRow = row.type === "trace";
22390
+ const rowClassName = cn(
22391
+ "hierarchy-timeline-row",
22392
+ className,
22393
+ isTraceRow ? "is-clickable" : "is-structure",
22394
+ row.depth === 0 && "is-root",
22395
+ row.isActive && "is-active",
22396
+ row.isDetailTrace && "is-detail-trace",
22397
+ row.isInPath && "is-in-path",
22398
+ `is-${row.type.replace(/[^a-z0-9-]/gi, "-")}`
22072
22399
  );
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
- }
22400
+ const rowStyle = {
22401
+ "--timeline-depth": String(row.depth),
22402
+ "--timeline-offset": String(
22403
+ model.durationMs > 0 ? row.offsetMs / model.durationMs : 0
22404
+ ),
22405
+ "--timeline-span": String(
22406
+ model.durationMs > 0 ? row.durationMs / model.durationMs : 1
22407
+ )
22408
+ };
22409
+ const rowContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react3.Fragment, { children: [
22410
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-time", children: formatTimelineTimestamp(row.startedAt) }),
22411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-branch", children: [
22412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-gutter", "aria-hidden": "true", children: [
22413
+ row.ancestorContinuations.map((continues, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22414
+ "span",
22415
+ {
22416
+ className: cn(
22417
+ "hierarchy-timeline-row-ancestor",
22418
+ continues && "has-line"
22220
22419
  ),
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",
22420
+ style: {
22421
+ "--timeline-connector-index": String(index)
22422
+ }
22423
+ },
22424
+ `${row.id}-ancestor-${index}`
22425
+ )),
22426
+ row.depth > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22427
+ "span",
22428
+ {
22429
+ className: "hierarchy-timeline-row-connector",
22430
+ style: {
22431
+ "--timeline-connector-index": String(row.depth - 1)
22432
+ },
22433
+ children: [
22434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-connector-top" }),
22435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-connector-elbow" }),
22436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22437
+ "span",
22234
22438
  {
22235
- type: "button",
22236
- className: rowClassName,
22237
- style: rowStyle,
22238
- onClick: () => onSelectRow(row.id),
22239
- children: rowContent
22439
+ className: cn(
22440
+ "hierarchy-timeline-row-connector-bottom",
22441
+ (row.hasVisibleChildren || !row.isLastSibling) && "has-line"
22442
+ )
22240
22443
  }
22241
22444
  )
22242
- },
22243
- row.id
22244
- );
22245
- }
22246
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22247
- "div",
22445
+ ]
22446
+ }
22447
+ ) : null
22448
+ ] }),
22449
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-labels", children: [
22450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-title", children: [
22451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hierarchy-timeline-row-title-text", children: row.label }),
22452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22453
+ Badge,
22454
+ {
22455
+ variant: "secondary",
22456
+ className: "hierarchy-timeline-pill",
22457
+ semantic: row.badge,
22458
+ children: row.badge
22459
+ }
22460
+ ),
22461
+ row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22462
+ "span",
22463
+ {
22464
+ className: "hierarchy-timeline-row-flag",
22465
+ title: "Structured input detected for this call",
22466
+ children: "Structured input"
22467
+ }
22468
+ ) : null,
22469
+ row.hasHighlights && !row.hasStructuredInput ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22470
+ "span",
22471
+ {
22472
+ className: "hierarchy-timeline-row-flag is-highlight",
22473
+ title: "Trace insights available",
22474
+ children: "Insight"
22475
+ }
22476
+ ) : null
22477
+ ] }),
22478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-meta", children: row.meta })
22479
+ ] })
22480
+ ] }),
22481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hierarchy-timeline-row-bars", children: [
22482
+ /* @__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" }) }),
22483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hierarchy-timeline-row-duration", children: formatElapsedLabel(row.durationMs) })
22484
+ ] })
22485
+ ] });
22486
+ if (isTraceRow) {
22487
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22488
+ "div",
22489
+ {
22490
+ role: "listitem",
22491
+ title: buildHierarchyTimelineRowTooltip(row),
22492
+ "aria-current": row.isActive || row.isDetailTrace ? "true" : void 0,
22493
+ "data-hierarchy-row-id": row.id,
22494
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22495
+ "button",
22248
22496
  {
22249
- role: "listitem",
22497
+ type: "button",
22250
22498
  className: rowClassName,
22251
22499
  style: rowStyle,
22252
- title: buildHierarchyTimelineRowTooltip(row),
22253
- "aria-current": row.isActive || row.isDetailTrace ? "true" : void 0,
22254
- "data-hierarchy-row-id": row.id,
22500
+ onClick: () => onSelectRow(row.id),
22255
22501
  children: rowContent
22256
- },
22257
- row.id
22258
- );
22259
- })
22260
- }
22261
- )
22262
- ] });
22502
+ }
22503
+ )
22504
+ },
22505
+ row.id
22506
+ );
22507
+ }
22508
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22509
+ "div",
22510
+ {
22511
+ role: "listitem",
22512
+ className: rowClassName,
22513
+ style: rowStyle,
22514
+ title: buildHierarchyTimelineRowTooltip(row),
22515
+ "aria-current": row.isActive || row.isDetailTrace ? "true" : void 0,
22516
+ "data-hierarchy-row-id": row.id,
22517
+ children: rowContent
22518
+ },
22519
+ row.id
22520
+ );
22521
+ });
22263
22522
  }
22264
22523
  function TraceDetailPanel({
22265
22524
  activeTab,
@@ -22271,10 +22530,8 @@ function TraceDetailPanel({
22271
22530
  onApplyTraceFilter,
22272
22531
  onBack,
22273
22532
  onNavigateHierarchyNode,
22274
- onSelectTimelineNode,
22275
22533
  onTabChange,
22276
- onToggleJsonMode,
22277
- timelineModel
22534
+ onToggleJsonMode
22278
22535
  }) {
22279
22536
  const traceDetailPrimaryRef = (0, import_react3.useRef)(null);
22280
22537
  const [showInlineContextRail, setShowInlineContextRail] = (0, import_react3.useState)(false);
@@ -22284,7 +22541,6 @@ function TraceDetailPanel({
22284
22541
  const detailSubtitle = formatTraceProviderSummary(detail ?? fallbackTrace);
22285
22542
  const detailCostUsd = detail ? getUsageCostUsd(detail.usage) : fallbackTrace?.costUsd ?? null;
22286
22543
  const detailCostLabel = formatUsdCost(detailCostUsd);
22287
- const hasSecondaryInspector = Boolean(detail && timelineModel);
22288
22544
  const detailFilterSource = detail ?? fallbackTrace;
22289
22545
  const canInlineContextRailTab = activeTab === "conversation" || activeTab === "request" || activeTab === "response";
22290
22546
  const visibleDetailTabs = canInlineContextRailTab && showInlineContextRail ? detailTabs.filter((tab) => tab.id !== "context") : detailTabs;
@@ -22386,77 +22642,59 @@ function TraceDetailPanel({
22386
22642
  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
22643
  ] }),
22388
22644
  /* @__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,
22645
+ /* @__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: [
22646
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "trace-detail-toolbar", children: [
22647
+ /* @__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)) }) }) }),
22648
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22649
+ Button,
22650
+ {
22651
+ variant: "outline",
22652
+ className: "trace-detail-json-toggle",
22653
+ onClick: () => onToggleJsonMode(activeTab),
22654
+ children: [
22655
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Boxes, { "data-icon": "inline-start" }),
22656
+ jsonMode === "formatted" ? "Raw JSON" : "Formatted"
22657
+ ]
22658
+ }
22659
+ )
22660
+ ] }),
22661
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator, {}),
22662
+ /* @__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: [
22663
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "detail-inline-context-main", children: renderTabContent(
22664
+ activeTab,
22665
+ detail,
22666
+ jsonMode,
22667
+ onApplyTagFilter,
22668
+ onApplyTraceFilter
22669
+ ) }),
22670
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
22671
+ "aside",
22672
+ {
22673
+ className: "conversation-context-rail",
22674
+ "aria-label": "Context summary",
22675
+ children: [
22676
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "conversation-context-rail-header", children: [
22677
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-context-rail-title", children: "Context" }),
22678
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "conversation-context-rail-copy", children: "Key trace metadata alongside the conversation." })
22679
+ ] }),
22680
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22681
+ ContextInspectorView,
22402
22682
  {
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
- ]
22683
+ detail,
22684
+ onApplyTagFilter
22410
22685
  }
22411
22686
  )
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)(
22687
+ ]
22688
+ }
22689
+ )
22690
+ ] }) : renderTabContent(
22691
+ activeTab,
22692
+ detail,
22693
+ jsonMode,
22694
+ onApplyTagFilter,
22695
+ onApplyTraceFilter
22696
+ ) }) })
22697
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "trace-detail-empty", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
22460
22698
  EmptyState,
22461
22699
  {
22462
22700
  icon: ArrowUpRight,
@@ -23528,52 +23766,6 @@ function buildDetailTabs(detail) {
23528
23766
  }
23529
23767
  return tabs;
23530
23768
  }
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
23769
  function getHierarchyNodeCopy(node, traceById) {
23578
23770
  switch (node.type) {
23579
23771
  case "session":
@@ -23710,40 +23902,6 @@ function formatTraceProviderSummary(trace) {
23710
23902
  }
23711
23903
  return formatList([trace.provider, trace.model]);
23712
23904
  }
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
23905
  function getTraceActorLabel(trace) {
23748
23906
  if (trace.kind === "guardrail") {
23749
23907
  return `${capitalize(trace.hierarchy.guardrailPhase || "guardrail")} guardrail`;
@@ -24287,9 +24445,6 @@ async function copyText(value) {
24287
24445
  return false;
24288
24446
  }
24289
24447
  }
24290
- function toSessionNodeId(sessionId) {
24291
- return `session:${sessionId}`;
24292
- }
24293
24448
  function toTraceNodeId(traceId) {
24294
24449
  return `trace:${traceId}`;
24295
24450
  }
@@ -24784,12 +24939,6 @@ function getSemanticBadgeValue(value) {
24784
24939
  return null;
24785
24940
  }
24786
24941
  }
24787
- function formatCompactTimestamp2(value) {
24788
- return new Date(value).toLocaleTimeString([], {
24789
- hour: "2-digit",
24790
- minute: "2-digit"
24791
- });
24792
- }
24793
24942
  function formatTimelineTimestamp(value) {
24794
24943
  return new Date(value).toLocaleTimeString([], {
24795
24944
  hour: "2-digit",
@@ -24803,6 +24952,14 @@ function getMaxDurationMs(items) {
24803
24952
  );
24804
24953
  return durations.length ? Math.max(...durations) : 1;
24805
24954
  }
24955
+ function getElapsedScale(durationMs, maxDurationMs) {
24956
+ if (durationMs === null || durationMs <= 0 || !Number.isFinite(durationMs)) {
24957
+ return 0.16;
24958
+ }
24959
+ const safeMax = Math.max(maxDurationMs, durationMs, 1);
24960
+ const ratio = Math.log1p(durationMs) / Math.log1p(safeMax);
24961
+ return Math.max(0.14, Math.min(1, ratio));
24962
+ }
24806
24963
  function formatElapsedLabel(durationMs) {
24807
24964
  if (durationMs === null || !Number.isFinite(durationMs)) {
24808
24965
  return "Running";
@@ -24993,7 +25150,7 @@ function parseEvent(data) {
24993
25150
  return null;
24994
25151
  }
24995
25152
  }
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;
25153
+ 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
25154
  var init_app = __esm({
24998
25155
  "client/src/app.tsx"() {
24999
25156
  init_lucide_react();
@@ -25011,8 +25168,6 @@ var init_app = __esm({
25011
25168
  MESSAGE_COLLAPSE_LINE_LIMIT_SYSTEM = 5;
25012
25169
  MESSAGE_COLLAPSE_HEIGHT_PROSE_SYSTEM = "9rem";
25013
25170
  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
25171
  EMPTY_TRACE_INSIGHTS = {
25017
25172
  highlights: [],
25018
25173
  structuredInputs: []