@industry-theme/principal-view-panels 0.12.31 → 0.12.33

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.
@@ -0,0 +1,44 @@
1
+ import type { Theme } from '@principal-ade/industry-theme';
2
+ import type { OtelSpanData } from '@principal-ai/principal-view-core';
3
+ import type { RegisteredTrace } from '../types/otel';
4
+ /**
5
+ * Color configuration for the tape
6
+ */
7
+ interface TraceTapeColors {
8
+ line?: string;
9
+ scrubber?: string;
10
+ highlighted?: string;
11
+ background?: string;
12
+ }
13
+ /**
14
+ * Props for the TraceTape component
15
+ */
16
+ export interface TraceTapeProps {
17
+ /** Array of traces to display on the tape */
18
+ traces: RegisteredTrace[];
19
+ /** Theme for styling */
20
+ theme: Theme;
21
+ /** Currently highlighted span ID (controlled from parent) */
22
+ highlightedSpanId?: string;
23
+ /** Currently selected trace ID - its spans will glow */
24
+ selectedTraceId?: string;
25
+ /** Trace ID to focus/jump to (changes trigger scrubber movement) */
26
+ focusTraceId?: string | null;
27
+ /** Callback when user scrubs to a new span */
28
+ onSpanHighlight: (spanId: string | null, span: OtelSpanData | null) => void;
29
+ /** Height of the tape in pixels */
30
+ height?: number;
31
+ /** Whether the tape is interactive */
32
+ interactive?: boolean;
33
+ /** Color scheme configuration (overrides theme-based colors) */
34
+ colors?: TraceTapeColors;
35
+ }
36
+ /**
37
+ * TraceTape - A horizontal timeline scrubber for navigating trace spans
38
+ *
39
+ * Displays all trace spans and events as vertical lines on a horizontal tape.
40
+ * Dragging the scrubber highlights the nearest span to the left of the cursor.
41
+ */
42
+ export declare function TraceTape({ traces, theme, highlightedSpanId, selectedTraceId, focusTraceId, onSpanHighlight, height, interactive, colors, }: TraceTapeProps): import("react/jsx-runtime").JSX.Element;
43
+ export default TraceTape;
44
+ //# sourceMappingURL=TraceTape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TraceTape.d.ts","sourceRoot":"","sources":["../../src/components/TraceTape.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD;;GAEG;AACH,UAAU,eAAe;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,wBAAwB;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8CAA8C;IAC9C,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5E,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gEAAgE;IAChE,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAUD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,KAAK,EACL,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,eAAe,EACf,MAAW,EACX,WAAkB,EAClB,MAAW,GACZ,EAAE,cAAc,2CA8ahB;AAED,eAAe,SAAS,CAAC"}
@@ -2,4 +2,6 @@ export { TraceList } from './TraceList';
2
2
  export type { TraceListProps } from './TraceList';
3
3
  export { TraceDetails } from './TraceDetails';
4
4
  export type { TraceDetailsProps, ScopeInfo } from './TraceDetails';
5
+ export { TraceTape } from './TraceTape';
6
+ export type { TraceTapeProps } from './TraceTape';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEnE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"TraceListPanel.d.ts","sourceRoot":"","sources":["../../src/panels/TraceListPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAczD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAgnC7D,CAAC"}
1
+ {"version":3,"file":"TraceListPanel.d.ts","sourceRoot":"","sources":["../../src/panels/TraceListPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAgBzD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAurC7D,CAAC"}
@@ -86468,16 +86468,43 @@ const CustomNode = ({ data, selected: selected2, dragging }) => {
86468
86468
  };
86469
86469
  }
86470
86470
  };
86471
+ const getBadgePosition = (position2) => {
86472
+ const isDiamondShape = typeDefinition.shape === "diamond";
86473
+ if (isDiamondShape) {
86474
+ switch (position2) {
86475
+ case "top-left":
86476
+ return { top: "50%", left: 0, transform: "translate(-50%, -50%)" };
86477
+ case "top-right":
86478
+ return { top: "50%", right: 0, transform: "translate(50%, -50%)" };
86479
+ case "left":
86480
+ return { top: "50%", left: 0, transform: "translate(-50%, -50%)" };
86481
+ case "right":
86482
+ return { top: "50%", right: 0, transform: "translate(50%, -50%)" };
86483
+ }
86484
+ }
86485
+ switch (position2) {
86486
+ case "top-left":
86487
+ return { top: -6, left: -6 };
86488
+ case "top-right":
86489
+ return { top: -6, right: -6 };
86490
+ case "left":
86491
+ return { top: -6, left: -6 };
86492
+ case "right":
86493
+ return { top: -6, right: -6 };
86494
+ }
86495
+ };
86471
86496
  const renderSourcesBadge = () => {
86472
86497
  const sources2 = nodeData == null ? void 0 : nodeData.sources;
86473
86498
  if (!sources2 || sources2.length === 0)
86474
86499
  return null;
86475
86500
  const shapeStyles = getBadgeShapeStyles();
86501
+ const positionStyles = getBadgePosition("top-right");
86476
86502
  return jsx("div", { style: {
86477
86503
  position: "absolute",
86478
- top: -6,
86479
- right: -6,
86504
+ ...positionStyles,
86480
86505
  ...shapeStyles,
86506
+ // Override transform if shape has rotation but we already have a position transform
86507
+ ...typeDefinition.shape === "diamond" ? { transform: `${positionStyles.transform} rotate(45deg)` } : {},
86481
86508
  backgroundColor: "#10b981",
86482
86509
  color: "white",
86483
86510
  fontSize: theme2.fontSizes[0],
@@ -86499,11 +86526,10 @@ const CustomNode = ({ data, selected: selected2, dragging }) => {
86499
86526
  const isOutbound = direction === "outbound";
86500
86527
  const directionIcon = isOutbound ? "↗" : "↙";
86501
86528
  const directionTitle = isOutbound ? "Outbound boundary (calls external system)" : "Inbound boundary (called by external system)";
86529
+ const positionStyles = getBadgePosition(isOutbound ? "right" : "left");
86502
86530
  return jsx("div", { style: {
86503
86531
  position: "absolute",
86504
- top: -6,
86505
- // Inbound badge on left, outbound badge on right
86506
- ...isOutbound ? { right: -6 } : { left: -6 },
86532
+ ...positionStyles,
86507
86533
  width: 18,
86508
86534
  height: 18,
86509
86535
  borderRadius: "50%",
@@ -86542,11 +86568,13 @@ const CustomNode = ({ data, selected: selected2, dragging }) => {
86542
86568
  implemented: "Implemented - Code exists"
86543
86569
  };
86544
86570
  const shapeStyles = getBadgeShapeStyles();
86571
+ const positionStyles = getBadgePosition("top-left");
86545
86572
  return jsx("div", { style: {
86546
86573
  position: "absolute",
86547
- top: -6,
86548
- left: -6,
86574
+ ...positionStyles,
86549
86575
  ...shapeStyles,
86576
+ // Override transform if shape has rotation but we already have a position transform
86577
+ ...typeDefinition.shape === "diamond" ? { transform: `${positionStyles.transform} rotate(45deg)` } : {},
86550
86578
  backgroundColor: statusColors[status2],
86551
86579
  color: "white",
86552
86580
  fontSize: theme2.fontSizes[0],
@@ -100928,6 +100956,361 @@ const TraceList = ({
100928
100956
  }
100929
100957
  );
100930
100958
  };
100959
+ function parseNanoTime(nanoStr) {
100960
+ const nanos = BigInt(nanoStr);
100961
+ return Number(nanos / BigInt(1e6));
100962
+ }
100963
+ function TraceTape({
100964
+ traces,
100965
+ theme: theme2,
100966
+ highlightedSpanId,
100967
+ selectedTraceId,
100968
+ focusTraceId,
100969
+ onSpanHighlight,
100970
+ height = 48,
100971
+ interactive = true,
100972
+ colors: colors2 = {}
100973
+ }) {
100974
+ var _a;
100975
+ const tapeRef = useRef(null);
100976
+ const [scrubberPercent, setScrubberPercent] = useState(null);
100977
+ const [isDragging, setIsDragging] = useState(false);
100978
+ const prevFocusTraceIdRef = useRef(void 0);
100979
+ const resolvedColors = {
100980
+ line: theme2.colors.primary,
100981
+ scrubber: theme2.colors.accent || "#22d3ee",
100982
+ highlighted: theme2.colors.text,
100983
+ background: theme2.colors.backgroundSecondary,
100984
+ ...colors2
100985
+ };
100986
+ const timeRange = useMemo(() => {
100987
+ if (traces.length === 0) return { min: 0, max: 0 };
100988
+ const allStartTimes = traces.map((t) => t.startTime);
100989
+ const allEndTimes = traces.map((t) => t.endTime);
100990
+ return {
100991
+ min: Math.min(...allStartTimes),
100992
+ max: Math.max(...allEndTimes)
100993
+ };
100994
+ }, [traces]);
100995
+ const spanIndexToPercent = useCallback((index2, total) => {
100996
+ if (total <= 1) return 50;
100997
+ const usableRange = 90;
100998
+ const startOffset = 5;
100999
+ return startOffset + index2 / (total - 1) * usableRange;
101000
+ }, []);
101001
+ const percentToSpanIndex = useCallback((percent, total) => {
101002
+ if (total <= 1) return 0;
101003
+ const usableRange = 90;
101004
+ const startOffset = 5;
101005
+ const normalizedPercent = Math.max(0, Math.min(100, percent));
101006
+ const index2 = Math.round((normalizedPercent - startOffset) / usableRange * (total - 1));
101007
+ return Math.max(0, Math.min(total - 1, index2));
101008
+ }, []);
101009
+ const spanMap = useMemo(() => {
101010
+ const map2 = /* @__PURE__ */ new Map();
101011
+ traces.forEach((trace2) => {
101012
+ const spans = getSpansFromTrace(trace2);
101013
+ spans.forEach((span) => map2.set(span.spanId, span));
101014
+ });
101015
+ return map2;
101016
+ }, [traces]);
101017
+ const spanBars = useMemo(() => {
101018
+ const bars = [];
101019
+ traces.forEach((trace2) => {
101020
+ const spans = getSpansFromTrace(trace2);
101021
+ if (spans.length > 0) {
101022
+ let earliestSpanId = spans[0].spanId;
101023
+ let earliestTime = parseNanoTime(spans[0].startTimeUnixNano);
101024
+ spans.forEach((span) => {
101025
+ const startMs = parseNanoTime(span.startTimeUnixNano);
101026
+ const endMs = parseNanoTime(span.endTimeUnixNano);
101027
+ if (startMs < earliestTime) {
101028
+ earliestTime = startMs;
101029
+ earliestSpanId = span.spanId;
101030
+ }
101031
+ bars.push({
101032
+ id: span.spanId,
101033
+ spanId: span.spanId,
101034
+ spanName: span.name,
101035
+ startMs,
101036
+ endMs,
101037
+ isRoot: false,
101038
+ // Will be set below
101039
+ traceId: trace2.traceId
101040
+ });
101041
+ });
101042
+ const rootBar = bars.find((b) => b.spanId === earliestSpanId);
101043
+ if (rootBar) rootBar.isRoot = true;
101044
+ } else {
101045
+ bars.push({
101046
+ id: `trace-${trace2.traceId}`,
101047
+ spanId: `trace-${trace2.traceId}`,
101048
+ spanName: trace2.name,
101049
+ startMs: trace2.startTime,
101050
+ endMs: trace2.endTime,
101051
+ isRoot: true,
101052
+ traceId: trace2.traceId
101053
+ });
101054
+ }
101055
+ });
101056
+ bars.sort((a, b) => a.startMs - b.startMs);
101057
+ return bars;
101058
+ }, [traces]);
101059
+ const findSpanByIndex = useCallback(
101060
+ (index2) => {
101061
+ if (index2 < 0 || index2 >= spanBars.length) return null;
101062
+ const bar = spanBars[index2];
101063
+ return spanMap.get(bar.spanId) || null;
101064
+ },
101065
+ [spanBars, spanMap]
101066
+ );
101067
+ const getSpanBarByIndex = useCallback(
101068
+ (index2) => {
101069
+ if (index2 < 0 || index2 >= spanBars.length) return null;
101070
+ return spanBars[index2];
101071
+ },
101072
+ [spanBars]
101073
+ );
101074
+ const getRelativePercent = useCallback((event) => {
101075
+ var _a2, _b;
101076
+ if (!tapeRef.current) return 0;
101077
+ const rect = tapeRef.current.getBoundingClientRect();
101078
+ const clientX = "touches" in event ? ((_a2 = event.touches[0]) == null ? void 0 : _a2.clientX) ?? ((_b = event.changedTouches[0]) == null ? void 0 : _b.clientX) ?? 0 : event.clientX;
101079
+ const x = clientX - rect.left;
101080
+ const percent = x / rect.width * 100;
101081
+ return Math.max(0, Math.min(100, percent));
101082
+ }, []);
101083
+ const handlePointerDown2 = useCallback(
101084
+ (event) => {
101085
+ if (!interactive) return;
101086
+ event.preventDefault();
101087
+ setIsDragging(true);
101088
+ const percent = getRelativePercent(event.nativeEvent);
101089
+ const spanIndex = percentToSpanIndex(percent, spanBars.length);
101090
+ const snappedPercent = spanIndexToPercent(spanIndex, spanBars.length);
101091
+ setScrubberPercent(snappedPercent);
101092
+ const span = findSpanByIndex(spanIndex);
101093
+ const bar = getSpanBarByIndex(spanIndex);
101094
+ onSpanHighlight((span == null ? void 0 : span.spanId) || (bar == null ? void 0 : bar.spanId) || null, span);
101095
+ },
101096
+ [
101097
+ interactive,
101098
+ percentToSpanIndex,
101099
+ spanIndexToPercent,
101100
+ spanBars.length,
101101
+ findSpanByIndex,
101102
+ getSpanBarByIndex,
101103
+ onSpanHighlight,
101104
+ getRelativePercent
101105
+ ]
101106
+ );
101107
+ const handlePointerMove2 = useCallback(
101108
+ (event) => {
101109
+ if (!isDragging || !tapeRef.current) return;
101110
+ event.preventDefault();
101111
+ const percent = getRelativePercent(event);
101112
+ const spanIndex = percentToSpanIndex(percent, spanBars.length);
101113
+ const snappedPercent = spanIndexToPercent(spanIndex, spanBars.length);
101114
+ setScrubberPercent(snappedPercent);
101115
+ const span = findSpanByIndex(spanIndex);
101116
+ const bar = getSpanBarByIndex(spanIndex);
101117
+ onSpanHighlight((span == null ? void 0 : span.spanId) || (bar == null ? void 0 : bar.spanId) || null, span);
101118
+ },
101119
+ [
101120
+ isDragging,
101121
+ percentToSpanIndex,
101122
+ spanIndexToPercent,
101123
+ spanBars.length,
101124
+ findSpanByIndex,
101125
+ getSpanBarByIndex,
101126
+ onSpanHighlight,
101127
+ getRelativePercent
101128
+ ]
101129
+ );
101130
+ const handlePointerUp2 = useCallback(() => {
101131
+ setIsDragging(false);
101132
+ }, []);
101133
+ useEffect(() => {
101134
+ if (focusTraceId === prevFocusTraceIdRef.current) return;
101135
+ prevFocusTraceIdRef.current = focusTraceId;
101136
+ if (!focusTraceId) return;
101137
+ const spanIndex = spanBars.findIndex((bar) => bar.traceId === focusTraceId);
101138
+ if (spanIndex !== -1) {
101139
+ const snappedPercent = spanIndexToPercent(spanIndex, spanBars.length);
101140
+ setScrubberPercent(snappedPercent);
101141
+ }
101142
+ }, [focusTraceId, spanBars, spanIndexToPercent]);
101143
+ useEffect(() => {
101144
+ if (isDragging) {
101145
+ window.addEventListener("mousemove", handlePointerMove2);
101146
+ window.addEventListener("mouseup", handlePointerUp2);
101147
+ window.addEventListener("touchmove", handlePointerMove2, { passive: false });
101148
+ window.addEventListener("touchend", handlePointerUp2);
101149
+ }
101150
+ return () => {
101151
+ window.removeEventListener("mousemove", handlePointerMove2);
101152
+ window.removeEventListener("mouseup", handlePointerUp2);
101153
+ window.removeEventListener("touchmove", handlePointerMove2);
101154
+ window.removeEventListener("touchend", handlePointerUp2);
101155
+ };
101156
+ }, [isDragging, handlePointerMove2, handlePointerUp2]);
101157
+ const formatDuration = (ms) => {
101158
+ const totalSeconds = Math.floor(ms / 1e3);
101159
+ const minutes = Math.floor(totalSeconds / 60);
101160
+ const seconds = totalSeconds % 60;
101161
+ const milliseconds = Math.floor(ms % 1e3);
101162
+ if (minutes > 0) {
101163
+ return `${minutes}:${String(seconds).padStart(2, "0")}.${String(milliseconds).padStart(3, "0")}`;
101164
+ }
101165
+ return `${seconds}.${String(milliseconds).padStart(3, "0")}`;
101166
+ };
101167
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
101168
+ /* @__PURE__ */ jsx(
101169
+ "div",
101170
+ {
101171
+ style: {
101172
+ fontSize: theme2.fontSizes[1],
101173
+ fontFamily: theme2.fonts.monospace,
101174
+ color: theme2.colors.textSecondary,
101175
+ whiteSpace: "nowrap",
101176
+ minWidth: "60px"
101177
+ },
101178
+ children: traces.length > 0 ? "0.000" : "--.---"
101179
+ }
101180
+ ),
101181
+ /* @__PURE__ */ jsxs(
101182
+ "div",
101183
+ {
101184
+ ref: tapeRef,
101185
+ onMouseDown: handlePointerDown2,
101186
+ onTouchStart: handlePointerDown2,
101187
+ style: {
101188
+ position: "relative",
101189
+ flex: 1,
101190
+ height: `${height}px`,
101191
+ background: resolvedColors.background,
101192
+ borderRadius: theme2.radii[2],
101193
+ overflow: "visible",
101194
+ cursor: interactive ? "pointer" : "default",
101195
+ userSelect: "none",
101196
+ touchAction: "none"
101197
+ },
101198
+ children: [
101199
+ traces.length === 0 && /* @__PURE__ */ jsx(
101200
+ "div",
101201
+ {
101202
+ style: {
101203
+ position: "absolute",
101204
+ inset: 0,
101205
+ display: "flex",
101206
+ alignItems: "center",
101207
+ justifyContent: "center",
101208
+ color: theme2.colors.textMuted,
101209
+ fontSize: theme2.fontSizes[2],
101210
+ fontFamily: theme2.fonts.body
101211
+ },
101212
+ children: "No traces"
101213
+ }
101214
+ ),
101215
+ spanBars.map((bar, index2) => {
101216
+ const percent = spanIndexToPercent(index2, spanBars.length);
101217
+ const isHighlighted = bar.spanId === highlightedSpanId;
101218
+ const isInSelectedTrace = bar.traceId === selectedTraceId;
101219
+ const topOffset = bar.isRoot ? "4px" : "50%";
101220
+ const bottomOffset = "4px";
101221
+ const traceColor = theme2.colors.accent || "#14b8a6";
101222
+ const spanColor = resolvedColors.line;
101223
+ const selectedTraceColor = theme2.colors.accent || "#5eead4";
101224
+ const selectedSpanColor = `${theme2.colors.primary}cc`;
101225
+ const getWidth = () => {
101226
+ if (isHighlighted) return "6px";
101227
+ if (isInSelectedTrace) return bar.isRoot ? "6px" : "4px";
101228
+ return bar.isRoot ? "4px" : "2px";
101229
+ };
101230
+ const getBackground = () => {
101231
+ if (isHighlighted) return resolvedColors.highlighted;
101232
+ if (isInSelectedTrace) return bar.isRoot ? selectedTraceColor : selectedSpanColor;
101233
+ return bar.isRoot ? traceColor : spanColor;
101234
+ };
101235
+ return /* @__PURE__ */ jsx(
101236
+ "div",
101237
+ {
101238
+ style: {
101239
+ position: "absolute",
101240
+ left: `${percent}%`,
101241
+ transform: "translateX(-50%)",
101242
+ top: topOffset,
101243
+ bottom: bottomOffset,
101244
+ width: getWidth(),
101245
+ background: getBackground(),
101246
+ opacity: isHighlighted ? 1 : isInSelectedTrace ? 1 : bar.isRoot ? 0.9 : 0.5,
101247
+ borderRadius: "2px",
101248
+ transition: "all 0.15s",
101249
+ pointerEvents: "none",
101250
+ boxShadow: isInSelectedTrace ? `0 0 8px 2px ${getBackground()}` : "none"
101251
+ },
101252
+ title: `${bar.isRoot ? "Trace: " : ""}${bar.spanName} (${(bar.endMs - bar.startMs).toFixed(1)}ms)`
101253
+ },
101254
+ bar.id
101255
+ );
101256
+ }),
101257
+ scrubberPercent !== null && /* @__PURE__ */ jsx(
101258
+ "div",
101259
+ {
101260
+ style: {
101261
+ position: "absolute",
101262
+ left: `${scrubberPercent}%`,
101263
+ top: 0,
101264
+ bottom: 0,
101265
+ width: "2px",
101266
+ marginLeft: "-1px",
101267
+ background: resolvedColors.scrubber,
101268
+ pointerEvents: "none",
101269
+ zIndex: 10
101270
+ },
101271
+ children: /* @__PURE__ */ jsx(
101272
+ "div",
101273
+ {
101274
+ style: {
101275
+ position: "absolute",
101276
+ top: "-24px",
101277
+ left: "50%",
101278
+ transform: "translateX(-50%)",
101279
+ fontSize: theme2.fontSizes[0],
101280
+ fontFamily: theme2.fonts.monospace,
101281
+ color: resolvedColors.scrubber,
101282
+ whiteSpace: "nowrap",
101283
+ background: theme2.colors.background,
101284
+ padding: "2px 6px",
101285
+ borderRadius: "3px",
101286
+ textTransform: "uppercase",
101287
+ letterSpacing: "0.5px",
101288
+ border: `1px solid ${theme2.colors.border}`
101289
+ },
101290
+ children: ((_a = spanBars[percentToSpanIndex(scrubberPercent, spanBars.length)]) == null ? void 0 : _a.isRoot) ? "trace" : "span"
101291
+ }
101292
+ )
101293
+ }
101294
+ )
101295
+ ]
101296
+ }
101297
+ ),
101298
+ /* @__PURE__ */ jsx(
101299
+ "div",
101300
+ {
101301
+ style: {
101302
+ fontSize: theme2.fontSizes[1],
101303
+ fontFamily: theme2.fonts.monospace,
101304
+ color: theme2.colors.textSecondary,
101305
+ whiteSpace: "nowrap",
101306
+ minWidth: "60px",
101307
+ textAlign: "right"
101308
+ },
101309
+ children: traces.length > 0 ? formatDuration(timeRange.max - timeRange.min) : "--.---"
101310
+ }
101311
+ )
101312
+ ] });
101313
+ }
100931
101314
  class PanelFileSystemAdapter {
100932
101315
  constructor(options) {
100933
101316
  this.fileContentCache = /* @__PURE__ */ new Map();
@@ -101158,6 +101541,8 @@ const TraceListPanel = ({
101158
101541
  const [activeTab, setActiveTab] = useState("traces");
101159
101542
  const [selectedSchematicNodeId, setSelectedSchematicNodeId] = useState(null);
101160
101543
  const [workflowFilterMode, setWorkflowFilterMode] = useState("all");
101544
+ const [highlightedSpanId, setHighlightedSpanId] = useState();
101545
+ const [focusTraceId, setFocusTraceId] = useState(null);
101161
101546
  const telemetrySlice = context2.telemetry;
101162
101547
  const traces = React__default.useMemo(() => (telemetrySlice == null ? void 0 : telemetrySlice.data) || [], [telemetrySlice == null ? void 0 : telemetrySlice.data]);
101163
101548
  const schematicsSlice = context2.schematics;
@@ -101389,6 +101774,39 @@ const TraceListPanel = ({
101389
101774
  setResources((prev) => ({ ...prev, [key.trim()]: "" }));
101390
101775
  }
101391
101776
  };
101777
+ const findTraceForSpan = (spanId) => {
101778
+ var _a;
101779
+ if (spanId.startsWith("trace-")) {
101780
+ const traceId = spanId.slice(6);
101781
+ return traces.find((t) => t.traceId === traceId) || null;
101782
+ }
101783
+ for (const trace2 of traces) {
101784
+ if ((_a = trace2.otlpData) == null ? void 0 : _a.resourceSpans) {
101785
+ for (const resourceSpan of trace2.otlpData.resourceSpans) {
101786
+ for (const scopeSpan of resourceSpan.scopeSpans) {
101787
+ if (scopeSpan.spans.some((s2) => s2.spanId === spanId)) {
101788
+ return trace2;
101789
+ }
101790
+ }
101791
+ }
101792
+ }
101793
+ }
101794
+ return null;
101795
+ };
101796
+ const handleSpanHighlight = (spanId, _span) => {
101797
+ setHighlightedSpanId(spanId ?? void 0);
101798
+ setFocusTraceId(null);
101799
+ if (spanId) {
101800
+ const trace2 = findTraceForSpan(spanId);
101801
+ if (trace2) {
101802
+ setExpandedTraceIds((prev) => {
101803
+ const newSet = new Set(prev);
101804
+ newSet.add(trace2.traceId);
101805
+ return newSet;
101806
+ });
101807
+ }
101808
+ }
101809
+ };
101392
101810
  const handleTraceClick = (trace2) => {
101393
101811
  setExpandedTraceIds((prev) => {
101394
101812
  const newSet = new Set(prev);
@@ -101399,6 +101817,7 @@ const TraceListPanel = ({
101399
101817
  }
101400
101818
  return newSet;
101401
101819
  });
101820
+ setFocusTraceId(trace2.traceId);
101402
101821
  };
101403
101822
  const handleTraceSelect = (trace2) => {
101404
101823
  if (events) {
@@ -101688,20 +102107,46 @@ const TraceListPanel = ({
101688
102107
  ]
101689
102108
  }
101690
102109
  ),
101691
- activeTab === "traces" ? /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto" }, children: /* @__PURE__ */ jsx(
101692
- TraceList,
101693
- {
101694
- traces,
101695
- theme: theme2,
101696
- onTraceClick: handleTraceClick,
101697
- onTraceSelect: handleTraceSelect,
101698
- onWorkflowClick: handleWorkflowClick,
101699
- onRemoveTrace: handleRemoveTrace,
101700
- onClearAll: handleClearAll,
101701
- expandedTraceIds,
101702
- emptyMessage: traces.length === 0 ? "No traces received yet. Waiting for telemetry data..." : void 0
101703
- }
101704
- ) }) : activeTab === "configuration" && showConfigurationTab ? /* @__PURE__ */ jsx("div", { style: { flex: 1, padding: "16px", overflow: "auto" }, children: configLoading ? /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }, children: /* @__PURE__ */ jsx("div", { style: { fontFamily: theme2.fonts.body, fontSize: theme2.fontSizes[1], color: theme2.colors.textSecondary }, children: "Loading resources..." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
102110
+ activeTab === "traces" ? /* @__PURE__ */ jsxs("div", { style: { flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }, children: [
102111
+ traces.length > 0 && /* @__PURE__ */ jsx(
102112
+ "div",
102113
+ {
102114
+ style: {
102115
+ padding: "16px 16px 8px 16px",
102116
+ paddingTop: "24px",
102117
+ // Extra space for scrubber label
102118
+ borderBottom: `1px solid ${theme2.colors.border}`,
102119
+ flexShrink: 0
102120
+ },
102121
+ children: /* @__PURE__ */ jsx(
102122
+ TraceTape,
102123
+ {
102124
+ traces,
102125
+ theme: theme2,
102126
+ highlightedSpanId,
102127
+ selectedTraceId: Array.from(expandedTraceIds)[0],
102128
+ focusTraceId,
102129
+ onSpanHighlight: handleSpanHighlight,
102130
+ height: 40
102131
+ }
102132
+ )
102133
+ }
102134
+ ),
102135
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto" }, children: /* @__PURE__ */ jsx(
102136
+ TraceList,
102137
+ {
102138
+ traces,
102139
+ theme: theme2,
102140
+ onTraceClick: handleTraceClick,
102141
+ onTraceSelect: handleTraceSelect,
102142
+ onWorkflowClick: handleWorkflowClick,
102143
+ onRemoveTrace: handleRemoveTrace,
102144
+ onClearAll: handleClearAll,
102145
+ expandedTraceIds,
102146
+ emptyMessage: traces.length === 0 ? "No traces received yet. Waiting for telemetry data..." : void 0
102147
+ }
102148
+ ) })
102149
+ ] }) : activeTab === "configuration" && showConfigurationTab ? /* @__PURE__ */ jsx("div", { style: { flex: 1, padding: "16px", overflow: "auto" }, children: configLoading ? /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }, children: /* @__PURE__ */ jsx("div", { style: { fontFamily: theme2.fonts.body, fontSize: theme2.fontSizes[1], color: theme2.colors.textSecondary }, children: "Loading resources..." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
101705
102150
  /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
101706
102151
  /* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px 0", fontFamily: theme2.fonts.heading, fontSize: theme2.fontSizes[3], fontWeight: theme2.fontWeights.semibold }, children: "Library Resources" }),
101707
102152
  /* @__PURE__ */ jsx("p", { style: { margin: 0, fontFamily: theme2.fonts.body, fontSize: theme2.fontSizes[1], color: theme2.colors.textSecondary }, children: "Configure OpenTelemetry and other resources in library.yaml" })