@assistant-ui/react-o11y 0.0.33 → 0.0.34

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.
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm install @assistant-ui/react-o11y
13
13
  ```tsx
14
14
  "use client";
15
15
 
16
- import { AuiProvider, useAui } from "@assistant-ui/store";
16
+ import { AuiProvider, AuiConfig } from "@assistant-ui/store";
17
17
  import { SpanPrimitive, SpanResource } from "@assistant-ui/react-o11y";
18
18
 
19
19
  const spans = [
@@ -22,9 +22,9 @@ const spans = [
22
22
  ];
23
23
 
24
24
  export function Waterfall() {
25
- const aui = useAui({ span: SpanResource({ spans }) });
25
+ const config = AuiConfig({ span: SpanResource({ spans }) });
26
26
  return (
27
- <AuiProvider value={aui}>
27
+ <AuiProvider extends={null} config={config}>
28
28
  <SpanPrimitive.Timeline>
29
29
  <SpanPrimitive.Children>
30
30
  {() => (
@@ -1,40 +1,36 @@
1
1
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
2
- import { AuiProvider, Derived, useAui } from "@assistant-ui/store";
2
+ import { AuiConfig, AuiProvider, Derived, useAui } from "@assistant-ui/store";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  //#region src/context/SpanByIndexProvider.tsx
5
5
  const SpanByIndexProvider = (t0) => {
6
- const $ = c(8);
6
+ const $ = c(7);
7
7
  const { index, children } = t0;
8
8
  const parentAui = useAui();
9
9
  let t1;
10
10
  if ($[0] !== index || $[1] !== parentAui) {
11
- t1 = Derived({
11
+ t1 = AuiConfig({ span: Derived({
12
12
  source: "span",
13
13
  query: { index },
14
14
  get: () => parentAui.span.child({ index })
15
- });
15
+ }) });
16
16
  $[0] = index;
17
17
  $[1] = parentAui;
18
18
  $[2] = t1;
19
19
  } else t1 = $[2];
20
+ const config = t1;
20
21
  let t2;
21
- if ($[3] !== t1) {
22
- t2 = { span: t1 };
23
- $[3] = t1;
24
- $[4] = t2;
25
- } else t2 = $[4];
26
- const aui = useAui(t2);
27
- let t3;
28
- if ($[5] !== aui || $[6] !== children) {
29
- t3 = /* @__PURE__ */ jsx(AuiProvider, {
30
- value: aui,
22
+ if ($[3] !== children || $[4] !== config || $[5] !== parentAui) {
23
+ t2 = /* @__PURE__ */ jsx(AuiProvider, {
24
+ extends: parentAui,
25
+ config,
31
26
  children
32
27
  });
33
- $[5] = aui;
34
- $[6] = children;
35
- $[7] = t3;
36
- } else t3 = $[7];
37
- return t3;
28
+ $[3] = children;
29
+ $[4] = config;
30
+ $[5] = parentAui;
31
+ $[6] = t2;
32
+ } else t2 = $[6];
33
+ return t2;
38
34
  };
39
35
  //#endregion
40
36
  export { SpanByIndexProvider };
@@ -1 +1 @@
1
- {"version":3,"file":"SpanByIndexProvider.js","names":["FC","PropsWithChildren","useAui","AuiProvider","Derived","SpanByIndexProvider","index","t0","$","_c","children","parentAui","t1","source","query","get","span","child","t2","aui","t3"],"sources":["../../src/context/SpanByIndexProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { useAui, AuiProvider, Derived } from \"@assistant-ui/store\";\n\nexport const SpanByIndexProvider: FC<PropsWithChildren<{ index: number }>> = ({\n index,\n children,\n}) => {\n const parentAui = useAui();\n\n const aui = useAui({\n span: Derived({\n source: \"span\",\n query: { index },\n get: () => parentAui.span.child({ index }),\n }),\n });\n\n return <AuiProvider value={aui}>{children}</AuiProvider>;\n};\n"],"mappings":";;;;AAGA,MAAaK,uBAAgEE,OAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAC,MAAA,EAAAH,OAAAI,aAAAH;CAI5E,MAAAI,YAAkBT,OAAO;CAAE,IAAAU;CAAA,IAAAJ,EAAA,OAAAF,SAAAE,EAAA,OAAAG,WAAA;EAGnBC,KAAAR,QAAQ;GAAAS,QACJ;GAAMC,OACP,EAAAR,MAAQ;GAACS,WACLJ,UAASK,KAAKC,MAAO,EAAAX,MAAQ,CAAC;EAC3C,CAAC;EAACE,EAAA,KAAAF;EAAAE,EAAA,KAAAG;EAAAH,EAAA,KAAAI;CAAA,OAAAA,KAAAJ,EAAA;CAAA,IAAAU;CAAA,IAAAV,EAAA,OAAAI,IAAA;EALeM,KAAA,EAAAF,MACXJ,GAKR;EAACJ,EAAA,KAAAI;EAAAJ,EAAA,KAAAU;CAAA,OAAAA,KAAAV,EAAA;CAND,MAAAW,MAAYjB,OAAOgB,EAMlB;CAAE,IAAAE;CAAA,IAAAZ,EAAA,OAAAW,OAAAX,EAAA,OAAAE,UAAA;EAEIU,KAAA,oBAAC,aAAD;GAAoBD,OAAAA;GAAMT;EAAd,CAAA;EAAqCF,EAAA,KAAAW;EAAAX,EAAA,KAAAE;EAAAF,EAAA,KAAAY;CAAA,OAAAA,KAAAZ,EAAA;CAAA,OAAjDY;AAAiD"}
1
+ {"version":3,"file":"SpanByIndexProvider.js","names":["FC","PropsWithChildren","useAui","AuiConfig","AuiProvider","Derived","SpanByIndexProvider","index","t0","$","_c","children","parentAui","t1","span","source","query","get","child","config","t2"],"sources":["../../src/context/SpanByIndexProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { useAui, AuiConfig, AuiProvider, Derived } from \"@assistant-ui/store\";\n\nexport const SpanByIndexProvider: FC<PropsWithChildren<{ index: number }>> = ({\n index,\n children,\n}) => {\n const parentAui = useAui();\n\n const config = AuiConfig({\n span: Derived({\n source: \"span\",\n query: { index },\n get: () => parentAui.span.child({ index }),\n }),\n });\n return (\n <AuiProvider extends={parentAui} config={config}>\n {children}\n </AuiProvider>\n );\n};\n"],"mappings":";;;;AAGA,MAAaM,uBAAgEE,OAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAC,MAAA,EAAAH,OAAAI,aAAAH;CAI5E,MAAAI,YAAkBV,OAAO;CAAE,IAAAW;CAAA,IAAAJ,EAAA,OAAAF,SAAAE,EAAA,OAAAG,WAAA;EAEZC,KAAAV,UAAU,EAAAW,MACjBT,QAAQ;GAAAU,QACJ;GAAMC,OACP,EAAAT,MAAQ;GAACU,WACLL,UAASE,KAAKI,MAAO,EAAAX,MAAQ,CAAC;EAC3C,CAAC,EACH,CAAC;EAACE,EAAA,KAAAF;EAAAE,EAAA,KAAAG;EAAAH,EAAA,KAAAI;CAAA,OAAAA,KAAAJ,EAAA;CANF,MAAAU,SAAeN;CAMZ,IAAAO;CAAA,IAAAX,EAAA,OAAAE,YAAAF,EAAA,OAAAU,UAAAV,EAAA,OAAAG,WAAA;EAEDQ,KAAA,oBAAC,aAAD;GAAsBR,SAAAA;GAAmBO;GACtCR;EADS,CAAA;EAEEF,EAAA,KAAAE;EAAAF,EAAA,KAAAU;EAAAV,EAAA,KAAAG;EAAAH,EAAA,KAAAW;CAAA,OAAAA,KAAAX,EAAA;CAAA,OAFdW;AAEc"}
@@ -1 +1 @@
1
- {"version":3,"file":"SpanResource.d.ts","names":[],"sources":["../../src/resources/SpanResource.tsx"],"mappings":";;;KAOY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;cA8LW,0CAAY,SAAA;EA/DhB,OAAA"}
1
+ {"version":3,"file":"SpanResource.d.ts","names":[],"sources":["../../src/resources/SpanResource.tsx"],"mappings":";;;KAOY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;cA6PW,0CAAY,SAAA;EAlEhB,OAAA"}
@@ -3,32 +3,68 @@ import { useState } from "@assistant-ui/tap/react-shim";
3
3
  import { useClientLookup } from "@assistant-ui/store";
4
4
  import { resource, withKey } from "@assistant-ui/tap";
5
5
  //#region src/resources/SpanResource.tsx
6
- function calculateDepth(spanId, spanMap, cache) {
6
+ function calculateDepth(spanId, parents, cache) {
7
7
  const cached = cache.get(spanId);
8
8
  if (cached !== void 0) return cached;
9
- const span = spanMap.get(spanId);
10
- if (!span?.parentSpanId) {
11
- cache.set(spanId, 0);
12
- return 0;
9
+ const path = [];
10
+ let current = spanId;
11
+ let depth = -1;
12
+ while (current !== null) {
13
+ const currentDepth = cache.get(current);
14
+ if (currentDepth !== void 0) {
15
+ depth = currentDepth;
16
+ break;
17
+ }
18
+ path.push(current);
19
+ current = parents.get(current) ?? null;
20
+ }
21
+ for (let index = path.length - 1; index >= 0; index--) {
22
+ depth += 1;
23
+ cache.set(path[index], depth);
13
24
  }
14
- const depth = calculateDepth(span.parentSpanId, spanMap, cache) + 1;
15
- cache.set(spanId, depth);
16
25
  return depth;
17
26
  }
27
+ function normalizeSpanParents(spanMap) {
28
+ const parents = /* @__PURE__ */ new Map();
29
+ for (const span of spanMap.values()) parents.set(span.id, span.parentSpanId && spanMap.has(span.parentSpanId) ? span.parentSpanId : null);
30
+ const resolved = /* @__PURE__ */ new Set();
31
+ for (const spanId of spanMap.keys()) {
32
+ if (resolved.has(spanId)) continue;
33
+ const path = [];
34
+ const pathIds = /* @__PURE__ */ new Set();
35
+ let current = spanId;
36
+ while (current !== null && !resolved.has(current)) {
37
+ if (pathIds.has(current)) {
38
+ parents.set(current, null);
39
+ break;
40
+ }
41
+ path.push(current);
42
+ pathIds.add(current);
43
+ current = parents.get(current) ?? null;
44
+ }
45
+ for (const id of path) resolved.add(id);
46
+ }
47
+ return parents;
48
+ }
18
49
  function enrichSpans(rawSpans) {
19
50
  const spanMap = /* @__PURE__ */ new Map();
20
51
  for (const span of rawSpans) spanMap.set(span.id, span);
52
+ const parents = normalizeSpanParents(spanMap);
21
53
  const depthCache = /* @__PURE__ */ new Map();
22
54
  const childrenCount = /* @__PURE__ */ new Map();
23
- for (const span of rawSpans) if (span.parentSpanId) childrenCount.set(span.parentSpanId, (childrenCount.get(span.parentSpanId) ?? 0) + 1);
55
+ for (const span of rawSpans) {
56
+ const parentSpanId = parents.get(span.id);
57
+ if (parentSpanId) childrenCount.set(parentSpanId, (childrenCount.get(parentSpanId) ?? 0) + 1);
58
+ }
24
59
  const allSpans = /* @__PURE__ */ new Map();
25
60
  let min = Infinity;
26
61
  let max = -Infinity;
27
62
  for (const span of rawSpans) {
28
- const depth = calculateDepth(span.id, spanMap, depthCache);
63
+ const depth = calculateDepth(span.id, parents, depthCache);
29
64
  const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;
30
65
  allSpans.set(span.id, {
31
66
  ...span,
67
+ parentSpanId: parents.get(span.id) ?? null,
32
68
  depth,
33
69
  hasChildren,
34
70
  isCollapsed: false
@@ -42,16 +78,17 @@ function enrichSpans(rawSpans) {
42
78
  if (max === min) max = min + 100;
43
79
  return {
44
80
  allSpans,
81
+ parents,
45
82
  timeRange: {
46
83
  min,
47
84
  max
48
85
  }
49
86
  };
50
87
  }
51
- function buildFlatList(allSpans, collapsedIds) {
88
+ function buildFlatList(allSpans, parents, collapsedIds) {
52
89
  const children = /* @__PURE__ */ new Map();
53
90
  for (const span of allSpans.values()) {
54
- const parent = span.parentSpanId ?? null;
91
+ const parent = parents.get(span.id) ?? null;
55
92
  let group = children.get(parent);
56
93
  if (!group) {
57
94
  group = [];
@@ -93,7 +130,7 @@ const useSpanChildResource = ({ span, timeRange, onToggleCollapse }) => {
93
130
  };
94
131
  const SpanChildResource = resource(useSpanChildResource);
95
132
  const useSpanResource = (t0) => {
96
- const $ = c(19);
133
+ const $ = c(20);
97
134
  const { spans } = t0;
98
135
  let t1;
99
136
  if ($[0] !== spans) {
@@ -101,18 +138,19 @@ const useSpanResource = (t0) => {
101
138
  $[0] = spans;
102
139
  $[1] = t1;
103
140
  } else t1 = $[1];
104
- const { allSpans, timeRange } = t1;
141
+ const { allSpans, parents, timeRange } = t1;
105
142
  const [collapsedIds, setCollapsedIds] = useState(_temp);
106
143
  let t2;
107
- if ($[2] !== allSpans || $[3] !== collapsedIds) {
108
- t2 = buildFlatList(allSpans, collapsedIds);
144
+ if ($[2] !== allSpans || $[3] !== collapsedIds || $[4] !== parents) {
145
+ t2 = buildFlatList(allSpans, parents, collapsedIds);
109
146
  $[2] = allSpans;
110
147
  $[3] = collapsedIds;
111
- $[4] = t2;
112
- } else t2 = $[4];
148
+ $[4] = parents;
149
+ $[5] = t2;
150
+ } else t2 = $[5];
113
151
  const visibleSpans = t2;
114
152
  let t3;
115
- if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
153
+ if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
116
154
  t3 = (spanId) => {
117
155
  setCollapsedIds((prev) => {
118
156
  const next = new Set(prev);
@@ -121,31 +159,31 @@ const useSpanResource = (t0) => {
121
159
  return next;
122
160
  });
123
161
  };
124
- $[5] = t3;
125
- } else t3 = $[5];
162
+ $[6] = t3;
163
+ } else t3 = $[6];
126
164
  const toggleCollapse = t3;
127
165
  let t4;
128
- if ($[6] !== timeRange || $[7] !== visibleSpans) {
166
+ if ($[7] !== timeRange || $[8] !== visibleSpans) {
129
167
  let t5;
130
- if ($[9] !== timeRange) {
168
+ if ($[10] !== timeRange) {
131
169
  t5 = (span) => withKey(span.id, SpanChildResource({
132
170
  span,
133
171
  timeRange,
134
172
  onToggleCollapse: toggleCollapse
135
173
  }));
136
- $[9] = timeRange;
137
- $[10] = t5;
138
- } else t5 = $[10];
174
+ $[10] = timeRange;
175
+ $[11] = t5;
176
+ } else t5 = $[11];
139
177
  t4 = visibleSpans.map(t5);
140
- $[6] = timeRange;
141
- $[7] = visibleSpans;
142
- $[8] = t4;
143
- } else t4 = $[8];
178
+ $[7] = timeRange;
179
+ $[8] = visibleSpans;
180
+ $[9] = t4;
181
+ } else t4 = $[9];
144
182
  const lookup = useClientLookup(t4);
145
183
  const t5 = timeRange.max - timeRange.min;
146
184
  const t6 = lookup.state.length > 0;
147
185
  let t7;
148
- if ($[11] !== lookup.state || $[12] !== t5 || $[13] !== t6 || $[14] !== timeRange) {
186
+ if ($[12] !== lookup.state || $[13] !== t5 || $[14] !== t6 || $[15] !== timeRange) {
149
187
  const rootState = {
150
188
  id: "__root__",
151
189
  parentSpanId: null,
@@ -162,23 +200,23 @@ const useSpanResource = (t0) => {
162
200
  timeRange
163
201
  };
164
202
  t7 = () => rootState;
165
- $[11] = lookup.state;
166
- $[12] = t5;
167
- $[13] = t6;
168
- $[14] = timeRange;
169
- $[15] = t7;
170
- } else t7 = $[15];
203
+ $[12] = lookup.state;
204
+ $[13] = t5;
205
+ $[14] = t6;
206
+ $[15] = timeRange;
207
+ $[16] = t7;
208
+ } else t7 = $[16];
171
209
  let t8;
172
- if ($[16] !== lookup.get || $[17] !== t7) {
210
+ if ($[17] !== lookup.get || $[18] !== t7) {
173
211
  t8 = {
174
212
  getState: t7,
175
213
  child: lookup.get,
176
214
  toggleCollapse: _temp2
177
215
  };
178
- $[16] = lookup.get;
179
- $[17] = t7;
180
- $[18] = t8;
181
- } else t8 = $[18];
216
+ $[17] = lookup.get;
217
+ $[18] = t7;
218
+ $[19] = t8;
219
+ } else t8 = $[19];
182
220
  return t8;
183
221
  };
184
222
  const SpanResource = resource(useSpanResource);
@@ -1 +1 @@
1
- {"version":3,"file":"SpanResource.js","names":["useMemo","useState","resource","withKey","useClientLookup","ClientOutput","SpanItemState","SpanState","SpanData","id","parentSpanId","name","type","status","startedAt","endedAt","latencyMs","calculateDepth","spanId","spanMap","Map","cache","cached","get","undefined","span","set","depth","enrichSpans","rawSpans","allSpans","timeRange","min","max","depthCache","childrenCount","Infinity","hasChildren","isCollapsed","end","Date","now","buildFlatList","collapsedIds","Set","children","values","parent","group","push","sort","a","b","result","dfs","parentId","kids","has","useSpanChildResource","onToggleCollapse","state","getState","child","Error","toggleCollapse","SpanChildResource","useSpanResource","t0","$","_c","spans","t1","setCollapsedIds","_temp","t2","visibleSpans","t3","Symbol","for","prev","next","delete","add","t4","t5","map","lookup","t6","length","t7","rootState","t8","_temp2","SpanResource"],"sources":["../../src/resources/SpanResource.tsx"],"sourcesContent":["import \"../o11y-scope\";\n\nimport { useMemo, useState } from \"react\";\nimport { resource, withKey } from \"@assistant-ui/tap\";\nimport { useClientLookup, type ClientOutput } from \"@assistant-ui/store\";\nimport type { SpanItemState, SpanState } from \"../o11y-scope\";\n\nexport type SpanData = {\n id: string;\n parentSpanId: string | null;\n name: string;\n type: string;\n status: \"running\" | \"completed\" | \"failed\" | \"skipped\";\n startedAt: number;\n endedAt: number | null;\n latencyMs: number | null;\n};\n\nfunction calculateDepth(\n spanId: string,\n spanMap: Map<string, SpanData>,\n cache: Map<string, number>,\n): number {\n const cached = cache.get(spanId);\n if (cached !== undefined) return cached;\n\n const span = spanMap.get(spanId);\n if (!span?.parentSpanId) {\n cache.set(spanId, 0);\n return 0;\n }\n\n const depth = calculateDepth(span.parentSpanId, spanMap, cache) + 1;\n cache.set(spanId, depth);\n return depth;\n}\n\nfunction enrichSpans(rawSpans: SpanData[]): {\n allSpans: Map<string, SpanItemState>;\n timeRange: { min: number; max: number };\n} {\n const spanMap = new Map<string, SpanData>();\n for (const span of rawSpans) {\n spanMap.set(span.id, span);\n }\n\n const depthCache = new Map<string, number>();\n const childrenCount = new Map<string, number>();\n for (const span of rawSpans) {\n if (span.parentSpanId) {\n childrenCount.set(\n span.parentSpanId,\n (childrenCount.get(span.parentSpanId) ?? 0) + 1,\n );\n }\n }\n\n const allSpans = new Map<string, SpanItemState>();\n let min = Infinity;\n let max = -Infinity;\n\n for (const span of rawSpans) {\n const depth = calculateDepth(span.id, spanMap, depthCache);\n const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;\n allSpans.set(span.id, { ...span, depth, hasChildren, isCollapsed: false });\n\n if (span.startedAt < min) min = span.startedAt;\n const end = span.endedAt ?? Date.now();\n if (end > max) max = end;\n }\n\n if (min === Infinity) min = Date.now();\n if (max === -Infinity) max = Date.now();\n if (max === min) max = min + 100;\n\n return { allSpans, timeRange: { min, max } };\n}\n\nfunction buildFlatList(\n allSpans: Map<string, SpanItemState>,\n collapsedIds: Set<string>,\n): SpanItemState[] {\n const children = new Map<string | null, SpanItemState[]>();\n for (const span of allSpans.values()) {\n const parent = span.parentSpanId ?? null;\n let group = children.get(parent);\n if (!group) {\n group = [];\n children.set(parent, group);\n }\n group.push(span);\n }\n\n for (const group of children.values()) {\n group.sort((a, b) => a.startedAt - b.startedAt);\n }\n\n const result: SpanItemState[] = [];\n function dfs(parentId: string | null) {\n const kids = children.get(parentId);\n if (!kids) return;\n for (const span of kids) {\n result.push({ ...span, isCollapsed: collapsedIds.has(span.id) });\n if (!collapsedIds.has(span.id)) {\n dfs(span.id);\n }\n }\n }\n dfs(null);\n return result;\n}\n\nconst useSpanChildResource = ({\n span,\n timeRange,\n onToggleCollapse,\n}: {\n span: SpanItemState;\n timeRange: { min: number; max: number };\n onToggleCollapse: (spanId: string) => void;\n}): ClientOutput<\"span\"> => {\n const state: SpanState = {\n ...span,\n children: [],\n timeRange,\n };\n return {\n getState: () => state,\n child: () => {\n throw new Error(\"child spans do not have children in flat mode\");\n },\n toggleCollapse: () => {\n onToggleCollapse(span.id);\n },\n };\n};\n\nconst SpanChildResource = resource(useSpanChildResource);\n\nconst useSpanResource = ({\n spans,\n}: {\n spans: SpanData[];\n}): ClientOutput<\"span\"> => {\n const { allSpans, timeRange } = useMemo(() => enrichSpans(spans), [spans]);\n\n const [collapsedIds, setCollapsedIds] = useState<Set<string>>(\n () => new Set(),\n );\n\n const visibleSpans = useMemo(\n () => buildFlatList(allSpans, collapsedIds),\n [allSpans, collapsedIds],\n );\n\n const toggleCollapse = (spanId: string) => {\n setCollapsedIds((prev) => {\n const next = new Set(prev);\n if (next.has(spanId)) {\n next.delete(spanId);\n } else {\n next.add(spanId);\n }\n return next;\n });\n };\n\n const lookup = useClientLookup(\n visibleSpans.map((span) =>\n withKey(\n span.id,\n SpanChildResource({\n span,\n timeRange,\n onToggleCollapse: toggleCollapse,\n }),\n ),\n ),\n );\n\n const rootState: SpanState = {\n id: \"__root__\",\n parentSpanId: null,\n name: \"\",\n type: \"root\",\n status: \"completed\",\n startedAt: timeRange.min,\n endedAt: timeRange.max,\n latencyMs: timeRange.max - timeRange.min,\n depth: -1,\n hasChildren: lookup.state.length > 0,\n isCollapsed: false,\n children: lookup.state,\n timeRange,\n };\n\n return {\n getState: () => rootState,\n child: lookup.get,\n toggleCollapse: () => {\n // Root span collapse is a no-op\n },\n };\n};\n\nexport const SpanResource = resource(useSpanResource);\n"],"mappings":";;;;;AAkBA,SAASiB,eACPC,QACAC,SACAE,OACQ;CACR,MAAMC,SAASD,MAAME,IAAIL,MAAM;CAC/B,IAAII,WAAWE,KAAAA,GAAW,OAAOF;CAEjC,MAAMG,OAAON,QAAQI,IAAIL,MAAM;CAC/B,IAAI,CAACO,MAAMf,cAAc;EACvBW,MAAMK,IAAIR,QAAQ,CAAC;EACnB,OAAO;CACT;CAEA,MAAMS,QAAQV,eAAeQ,KAAKf,cAAcS,SAASE,KAAK,IAAI;CAClEA,MAAMK,IAAIR,QAAQS,KAAK;CACvB,OAAOA;AACT;AAEA,SAASC,YAAYC,UAGnB;CACA,MAAMV,0BAAU,IAAIC,IAAsB;CAC1C,KAAK,MAAMK,QAAQI,UACjBV,QAAQO,IAAID,KAAKhB,IAAIgB,IAAI;CAG3B,MAAMS,6BAAa,IAAId,IAAoB;CAC3C,MAAMe,gCAAgB,IAAIf,IAAoB;CAC9C,KAAK,MAAMK,QAAQI,UACjB,IAAIJ,KAAKf,cACPyB,cAAcT,IACZD,KAAKf,eACJyB,cAAcZ,IAAIE,KAAKf,YAAY,KAAK,KAAK,CAChD;CAIJ,MAAMoB,2BAAW,IAAIV,IAA2B;CAChD,IAAIY,MAAMI;CACV,IAAIH,MAAM;CAEV,KAAK,MAAMR,QAAQI,UAAU;EAC3B,MAAMF,QAAQV,eAAeQ,KAAKhB,IAAIU,SAASe,UAAU;EACzD,MAAMG,eAAeF,cAAcZ,IAAIE,KAAKhB,EAAE,KAAK,KAAK;EACxDqB,SAASJ,IAAID,KAAKhB,IAAI;GAAE,GAAGgB;GAAME;GAAOU;GAAaC,aAAa;EAAM,CAAC;EAEzE,IAAIb,KAAKX,YAAYkB,KAAKA,MAAMP,KAAKX;EACrC,MAAMyB,MAAMd,KAAKV,WAAWyB,KAAKC,IAAI;EACrC,IAAIF,MAAMN,KAAKA,MAAMM;CACvB;CAEA,IAAIP,QAAQI,UAAUJ,MAAMQ,KAAKC,IAAI;CACrC,IAAIR,QAAQ,WAAWA,MAAMO,KAAKC,IAAI;CACtC,IAAIR,QAAQD,KAAKC,MAAMD,MAAM;CAE7B,OAAO;EAAEF;EAAUC,WAAW;GAAEC;GAAKC;EAAI;CAAE;AAC7C;AAEA,SAASS,cACPZ,UACAa,cACiB;CACjB,MAAME,2BAAW,IAAIzB,IAAoC;CACzD,KAAK,MAAMK,QAAQK,SAASgB,OAAO,GAAG;EACpC,MAAMC,SAAStB,KAAKf,gBAAgB;EACpC,IAAIsC,QAAQH,SAAStB,IAAIwB,MAAM;EAC/B,IAAI,CAACC,OAAO;GACVA,QAAQ,CAAA;GACRH,SAASnB,IAAIqB,QAAQC,KAAK;EAC5B;EACAA,MAAMC,KAAKxB,IAAI;CACjB;CAEA,KAAK,MAAMuB,SAASH,SAASC,OAAO,GAClCE,MAAME,MAAMC,GAAGC,MAAMD,EAAErC,YAAYsC,EAAEtC,SAAS;CAGhD,MAAMuC,SAA0B,CAAA;CAChC,SAASC,IAAIC,UAAyB;EACpC,MAAMC,OAAOX,SAAStB,IAAIgC,QAAQ;EAClC,IAAI,CAACC,MAAM;EACX,KAAK,MAAM/B,QAAQ+B,MAAM;GACvBH,OAAOJ,KAAK;IAAE,GAAGxB;IAAMa,aAAaK,aAAac,IAAIhC,KAAKhB,EAAE;GAAE,CAAC;GAC/D,IAAI,CAACkC,aAAac,IAAIhC,KAAKhB,EAAE,GAC3B6C,IAAI7B,KAAKhB,EAAE;EAEf;CACF;CACA6C,IAAI,IAAI;CACR,OAAOD;AACT;AAEA,MAAMK,wBAAwB,EAC5BjC,MACAM,WACA4B,uBAK0B;CAC1B,MAAMC,QAAmB;EACvB,GAAGnC;EACHoB,UAAU,CAAA;EACVd;CACF;CACA,OAAO;EACL8B,gBAAgBD;EAChBE,aAAa;GACX,MAAM,IAAIC,MAAM,+CAA+C;EACjE;EACAC,sBAAsB;GACpBL,iBAAiBlC,KAAKhB,EAAE;EAC1B;CACF;AACF;AAEA,MAAMwD,oBAAoB/D,SAASwD,oBAAoB;AAEvD,MAAMQ,mBAAkBC,OAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CAAC,MAAA,EAAAC,UAAAH;CAIxB,IAAAI;CAAA,IAAAH,EAAA,OAAAE,OAAA;EAC+CC,KAAA3C,YAAY0C,KAAK;EAACF,EAAA,KAAAE;EAAAF,EAAA,KAAAG;CAAA,OAAAA,KAAAH,EAAA;CAAhE,MAAA,EAAAtC,UAAAC,cAA8CwC;CAE9C,MAAA,CAAA5B,cAAA6B,mBAAwCvE,SACtCwE,KACF;CAAE,IAAAC;CAAA,IAAAN,EAAA,OAAAtC,YAAAsC,EAAA,OAAAzB,cAAA;EAGM+B,KAAAhC,cAAcZ,UAAUa,YAAY;EAACyB,EAAA,KAAAtC;EAAAsC,EAAA,KAAAzB;EAAAyB,EAAA,KAAAM;CAAA,OAAAA,KAAAN,EAAA;CAD7C,MAAAO,eACQD;CAEN,IAAAE;CAAA,IAAAR,EAAA,OAAAS,OAAAC,IAAA,2BAAA,GAAA;EAEqBF,MAAA1D,WAAA;GACrBsD,iBAAgBO,SAAA;IACd,MAAAC,OAAa,IAAIpC,IAAImC,IAAI;IACzB,IAAIC,KAAIvB,IAAKvC,MAAM,GACjB8D,KAAIC,OAAQ/D,MAAM;SAElB8D,KAAIE,IAAKhE,MAAM;IAChB,OACM8D;GAAI,CACZ;EAAC;EACHZ,EAAA,KAAAQ;CAAA,OAAAA,KAAAR,EAAA;CAVD,MAAAJ,iBAAuBY;CAUrB,IAAAO;CAAA,IAAAf,EAAA,OAAArC,aAAAqC,EAAA,OAAAO,cAAA;EAAA,IAAAS;EAAA,IAAAhB,EAAA,OAAArC,WAAA;GAGiBqD,MAAA3D,SACftB,QACEsB,KAAIhB,IACJwD,kBAAkB;IAAAxC;IAAAM;IAAA4B,kBAGEK;GACpB,CAAC,CACH;GAACI,EAAA,KAAArC;GAAAqC,EAAA,MAAAgB;EAAA,OAAAA,KAAAhB,EAAA;EARHe,KAAAR,aAAYU,IAAKD,EASjB;EAAChB,EAAA,KAAArC;EAAAqC,EAAA,KAAAO;EAAAP,EAAA,KAAAe;CAAA,OAAAA,KAAAf,EAAA;CAVH,MAAAkB,SAAelF,gBACb+E,EAUF;CAUa,MAAAC,KAAArD,UAASE,MAAOF,UAASC;CAEvB,MAAAuD,KAAAD,OAAM1B,MAAM4B,SAAU;CAAC,IAAAC;CAAA,IAAArB,EAAA,QAAAkB,OAAA1B,SAAAQ,EAAA,QAAAgB,MAAAhB,EAAA,QAAAmB,MAAAnB,EAAA,QAAArC,WAAA;EAVtC,MAAA2D,YAA6B;GAAAjF,IACvB;GAAUC,cACA;GAAIC,MACZ;GAAEC,MACF;GAAMC,QACJ;GAAWC,WACRiB,UAASC;GAAIjB,SACfgB,UAASE;GAAIjB,WACXoE;GAA6BzD,OACjC;GAAEU,aACIkD;GAAuBjD,aACvB;GAAKO,UACRyC,OAAM1B;GAAM7B;EAExB;EAGY0D,WAAMC;EAAStB,EAAA,MAAAkB,OAAA1B;EAAAQ,EAAA,MAAAgB;EAAAhB,EAAA,MAAAmB;EAAAnB,EAAA,MAAArC;EAAAqC,EAAA,MAAAqB;CAAA,OAAAA,KAAArB,EAAA;CAAA,IAAAuB;CAAA,IAAAvB,EAAA,QAAAkB,OAAA/D,OAAA6C,EAAA,QAAAqB,IAAA;EADpBE,KAAA;GAAA9B,UACK4B;GAAe3B,OAClBwB,OAAM/D;GAAIyC,gBACD4B;EAGlB;EAACxB,EAAA,MAAAkB,OAAA/D;EAAA6C,EAAA,MAAAqB;EAAArB,EAAA,MAAAuB;CAAA,OAAAA,KAAAvB,EAAA;CAAA,OANMuB;AAMN;AAGH,MAAaE,eAAe3F,SAASgE,eAAe;AAlE5B,SAAAO,QAAA;CAAA,uBAQd,IAAI7B,IAAI;AAAC;AARK,SAAAgD,SAAA,CAAA"}
1
+ {"version":3,"file":"SpanResource.js","names":["useMemo","useState","resource","withKey","useClientLookup","ClientOutput","SpanItemState","SpanState","SpanData","id","parentSpanId","name","type","status","startedAt","endedAt","latencyMs","calculateDepth","spanId","parents","Map","cache","cached","get","undefined","path","current","depth","currentDepth","push","index","length","set","normalizeSpanParents","spanMap","span","values","has","resolved","Set","keys","pathIds","add","enrichSpans","rawSpans","allSpans","timeRange","min","max","depthCache","childrenCount","Infinity","hasChildren","isCollapsed","end","Date","now","buildFlatList","collapsedIds","children","parent","group","sort","a","b","result","dfs","parentId","kids","useSpanChildResource","onToggleCollapse","state","getState","child","Error","toggleCollapse","SpanChildResource","useSpanResource","t0","$","_c","spans","t1","setCollapsedIds","_temp","t2","visibleSpans","t3","Symbol","for","prev","next","delete","t4","t5","map","lookup","t6","t7","rootState","t8","_temp2","SpanResource"],"sources":["../../src/resources/SpanResource.tsx"],"sourcesContent":["import \"../o11y-scope\";\n\nimport { useMemo, useState } from \"react\";\nimport { resource, withKey } from \"@assistant-ui/tap\";\nimport { useClientLookup, type ClientOutput } from \"@assistant-ui/store\";\nimport type { SpanItemState, SpanState } from \"../o11y-scope\";\n\nexport type SpanData = {\n id: string;\n parentSpanId: string | null;\n name: string;\n type: string;\n status: \"running\" | \"completed\" | \"failed\" | \"skipped\";\n startedAt: number;\n endedAt: number | null;\n latencyMs: number | null;\n};\n\nfunction calculateDepth(\n spanId: string,\n parents: Map<string, string | null>,\n cache: Map<string, number>,\n): number {\n const cached = cache.get(spanId);\n if (cached !== undefined) return cached;\n\n const path: string[] = [];\n let current: string | null = spanId;\n let depth = -1;\n\n while (current !== null) {\n const currentDepth = cache.get(current);\n if (currentDepth !== undefined) {\n depth = currentDepth;\n break;\n }\n\n path.push(current);\n current = parents.get(current) ?? null;\n }\n\n for (let index = path.length - 1; index >= 0; index--) {\n depth += 1;\n cache.set(path[index]!, depth);\n }\n\n return depth;\n}\n\nfunction normalizeSpanParents(\n spanMap: Map<string, SpanData>,\n): Map<string, string | null> {\n const parents = new Map<string, string | null>();\n for (const span of spanMap.values()) {\n parents.set(\n span.id,\n span.parentSpanId && spanMap.has(span.parentSpanId)\n ? span.parentSpanId\n : null,\n );\n }\n\n const resolved = new Set<string>();\n for (const spanId of spanMap.keys()) {\n if (resolved.has(spanId)) continue;\n\n const path: string[] = [];\n const pathIds = new Set<string>();\n let current: string | null = spanId;\n\n while (current !== null && !resolved.has(current)) {\n if (pathIds.has(current)) {\n parents.set(current, null);\n break;\n }\n\n path.push(current);\n pathIds.add(current);\n current = parents.get(current) ?? null;\n }\n\n for (const id of path) resolved.add(id);\n }\n\n return parents;\n}\n\nfunction enrichSpans(rawSpans: SpanData[]): {\n allSpans: Map<string, SpanItemState>;\n parents: Map<string, string | null>;\n timeRange: { min: number; max: number };\n} {\n const spanMap = new Map<string, SpanData>();\n for (const span of rawSpans) {\n spanMap.set(span.id, span);\n }\n\n const parents = normalizeSpanParents(spanMap);\n const depthCache = new Map<string, number>();\n const childrenCount = new Map<string, number>();\n for (const span of rawSpans) {\n const parentSpanId = parents.get(span.id);\n if (parentSpanId) {\n childrenCount.set(\n parentSpanId,\n (childrenCount.get(parentSpanId) ?? 0) + 1,\n );\n }\n }\n\n const allSpans = new Map<string, SpanItemState>();\n let min = Infinity;\n let max = -Infinity;\n\n for (const span of rawSpans) {\n const depth = calculateDepth(span.id, parents, depthCache);\n const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;\n allSpans.set(span.id, {\n ...span,\n parentSpanId: parents.get(span.id) ?? null,\n depth,\n hasChildren,\n isCollapsed: false,\n });\n\n if (span.startedAt < min) min = span.startedAt;\n const end = span.endedAt ?? Date.now();\n if (end > max) max = end;\n }\n\n if (min === Infinity) min = Date.now();\n if (max === -Infinity) max = Date.now();\n if (max === min) max = min + 100;\n\n return { allSpans, parents, timeRange: { min, max } };\n}\n\nfunction buildFlatList(\n allSpans: Map<string, SpanItemState>,\n parents: Map<string, string | null>,\n collapsedIds: Set<string>,\n): SpanItemState[] {\n const children = new Map<string | null, SpanItemState[]>();\n for (const span of allSpans.values()) {\n const parent = parents.get(span.id) ?? null;\n let group = children.get(parent);\n if (!group) {\n group = [];\n children.set(parent, group);\n }\n group.push(span);\n }\n\n for (const group of children.values()) {\n group.sort((a, b) => a.startedAt - b.startedAt);\n }\n\n const result: SpanItemState[] = [];\n function dfs(parentId: string | null) {\n const kids = children.get(parentId);\n if (!kids) return;\n for (const span of kids) {\n result.push({ ...span, isCollapsed: collapsedIds.has(span.id) });\n if (!collapsedIds.has(span.id)) {\n dfs(span.id);\n }\n }\n }\n dfs(null);\n return result;\n}\n\nconst useSpanChildResource = ({\n span,\n timeRange,\n onToggleCollapse,\n}: {\n span: SpanItemState;\n timeRange: { min: number; max: number };\n onToggleCollapse: (spanId: string) => void;\n}): ClientOutput<\"span\"> => {\n const state: SpanState = {\n ...span,\n children: [],\n timeRange,\n };\n return {\n getState: () => state,\n child: () => {\n throw new Error(\"child spans do not have children in flat mode\");\n },\n toggleCollapse: () => {\n onToggleCollapse(span.id);\n },\n };\n};\n\nconst SpanChildResource = resource(useSpanChildResource);\n\nconst useSpanResource = ({\n spans,\n}: {\n spans: SpanData[];\n}): ClientOutput<\"span\"> => {\n const { allSpans, parents, timeRange } = useMemo(\n () => enrichSpans(spans),\n [spans],\n );\n\n const [collapsedIds, setCollapsedIds] = useState<Set<string>>(\n () => new Set(),\n );\n\n const visibleSpans = useMemo(\n () => buildFlatList(allSpans, parents, collapsedIds),\n [allSpans, parents, collapsedIds],\n );\n\n const toggleCollapse = (spanId: string) => {\n setCollapsedIds((prev) => {\n const next = new Set(prev);\n if (next.has(spanId)) {\n next.delete(spanId);\n } else {\n next.add(spanId);\n }\n return next;\n });\n };\n\n const lookup = useClientLookup(\n visibleSpans.map((span) =>\n withKey(\n span.id,\n SpanChildResource({\n span,\n timeRange,\n onToggleCollapse: toggleCollapse,\n }),\n ),\n ),\n );\n\n const rootState: SpanState = {\n id: \"__root__\",\n parentSpanId: null,\n name: \"\",\n type: \"root\",\n status: \"completed\",\n startedAt: timeRange.min,\n endedAt: timeRange.max,\n latencyMs: timeRange.max - timeRange.min,\n depth: -1,\n hasChildren: lookup.state.length > 0,\n isCollapsed: false,\n children: lookup.state,\n timeRange,\n };\n\n return {\n getState: () => rootState,\n child: lookup.get,\n toggleCollapse: () => {\n // Root span collapse is a no-op\n },\n };\n};\n\nexport const SpanResource = resource(useSpanResource);\n"],"mappings":";;;;;AAkBA,SAASiB,eACPC,QACAC,SACAE,OACQ;CACR,MAAMC,SAASD,MAAME,IAAIL,MAAM;CAC/B,IAAII,WAAWE,KAAAA,GAAW,OAAOF;CAEjC,MAAMG,OAAiB,CAAA;CACvB,IAAIC,UAAyBR;CAC7B,IAAIS,QAAQ;CAEZ,OAAOD,YAAY,MAAM;EACvB,MAAME,eAAeP,MAAME,IAAIG,OAAO;EACtC,IAAIE,iBAAiBJ,KAAAA,GAAW;GAC9BG,QAAQC;GACR;EACF;EAEAH,KAAKI,KAAKH,OAAO;EACjBA,UAAUP,QAAQI,IAAIG,OAAO,KAAK;CACpC;CAEA,KAAK,IAAII,QAAQL,KAAKM,SAAS,GAAGD,SAAS,GAAGA,SAAS;EACrDH,SAAS;EACTN,MAAMW,IAAIP,KAAKK,QAASH,KAAK;CAC/B;CAEA,OAAOA;AACT;AAEA,SAASM,qBACPC,SAC4B;CAC5B,MAAMf,0BAAU,IAAIC,IAA2B;CAC/C,KAAK,MAAMe,QAAQD,QAAQE,OAAO,GAChCjB,QAAQa,IACNG,KAAK1B,IACL0B,KAAKzB,gBAAgBwB,QAAQG,IAAIF,KAAKzB,YAAY,IAC9CyB,KAAKzB,eACL,IACN;CAGF,MAAM4B,2BAAW,IAAIC,IAAY;CACjC,KAAK,MAAMrB,UAAUgB,QAAQM,KAAK,GAAG;EACnC,IAAIF,SAASD,IAAInB,MAAM,GAAG;EAE1B,MAAMO,OAAiB,CAAA;EACvB,MAAMgB,0BAAU,IAAIF,IAAY;EAChC,IAAIb,UAAyBR;EAE7B,OAAOQ,YAAY,QAAQ,CAACY,SAASD,IAAIX,OAAO,GAAG;GACjD,IAAIe,QAAQJ,IAAIX,OAAO,GAAG;IACxBP,QAAQa,IAAIN,SAAS,IAAI;IACzB;GACF;GAEAD,KAAKI,KAAKH,OAAO;GACjBe,QAAQC,IAAIhB,OAAO;GACnBA,UAAUP,QAAQI,IAAIG,OAAO,KAAK;EACpC;EAEA,KAAK,MAAMjB,MAAMgB,MAAMa,SAASI,IAAIjC,EAAE;CACxC;CAEA,OAAOU;AACT;AAEA,SAASwB,YAAYC,UAInB;CACA,MAAMV,0BAAU,IAAId,IAAsB;CAC1C,KAAK,MAAMe,QAAQS,UACjBV,QAAQF,IAAIG,KAAK1B,IAAI0B,IAAI;CAG3B,MAAMhB,UAAUc,qBAAqBC,OAAO;CAC5C,MAAMe,6BAAa,IAAI7B,IAAoB;CAC3C,MAAM8B,gCAAgB,IAAI9B,IAAoB;CAC9C,KAAK,MAAMe,QAAQS,UAAU;EAC3B,MAAMlC,eAAeS,QAAQI,IAAIY,KAAK1B,EAAE;EACxC,IAAIC,cACFwC,cAAclB,IACZtB,eACCwC,cAAc3B,IAAIb,YAAY,KAAK,KAAK,CAC3C;CAEJ;CAEA,MAAMmC,2BAAW,IAAIzB,IAA2B;CAChD,IAAI2B,MAAMI;CACV,IAAIH,MAAM;CAEV,KAAK,MAAMb,QAAQS,UAAU;EAC3B,MAAMjB,QAAQV,eAAekB,KAAK1B,IAAIU,SAAS8B,UAAU;EACzD,MAAMG,eAAeF,cAAc3B,IAAIY,KAAK1B,EAAE,KAAK,KAAK;EACxDoC,SAASb,IAAIG,KAAK1B,IAAI;GACpB,GAAG0B;GACHzB,cAAcS,QAAQI,IAAIY,KAAK1B,EAAE,KAAK;GACtCkB;GACAyB;GACAC,aAAa;EACf,CAAC;EAED,IAAIlB,KAAKrB,YAAYiC,KAAKA,MAAMZ,KAAKrB;EACrC,MAAMwC,MAAMnB,KAAKpB,WAAWwC,KAAKC,IAAI;EACrC,IAAIF,MAAMN,KAAKA,MAAMM;CACvB;CAEA,IAAIP,QAAQI,UAAUJ,MAAMQ,KAAKC,IAAI;CACrC,IAAIR,QAAQ,WAAWA,MAAMO,KAAKC,IAAI;CACtC,IAAIR,QAAQD,KAAKC,MAAMD,MAAM;CAE7B,OAAO;EAAEF;EAAU1B;EAAS2B,WAAW;GAAEC;GAAKC;EAAI;CAAE;AACtD;AAEA,SAASS,cACPZ,UACA1B,SACAuC,cACiB;CACjB,MAAMC,2BAAW,IAAIvC,IAAoC;CACzD,KAAK,MAAMe,QAAQU,SAAST,OAAO,GAAG;EACpC,MAAMwB,SAASzC,QAAQI,IAAIY,KAAK1B,EAAE,KAAK;EACvC,IAAIoD,QAAQF,SAASpC,IAAIqC,MAAM;EAC/B,IAAI,CAACC,OAAO;GACVA,QAAQ,CAAA;GACRF,SAAS3B,IAAI4B,QAAQC,KAAK;EAC5B;EACAA,MAAMhC,KAAKM,IAAI;CACjB;CAEA,KAAK,MAAM0B,SAASF,SAASvB,OAAO,GAClCyB,MAAMC,MAAMC,GAAGC,MAAMD,EAAEjD,YAAYkD,EAAElD,SAAS;CAGhD,MAAMmD,SAA0B,CAAA;CAChC,SAASC,IAAIC,UAAyB;EACpC,MAAMC,OAAOT,SAASpC,IAAI4C,QAAQ;EAClC,IAAI,CAACC,MAAM;EACX,KAAK,MAAMjC,QAAQiC,MAAM;GACvBH,OAAOpC,KAAK;IAAE,GAAGM;IAAMkB,aAAaK,aAAarB,IAAIF,KAAK1B,EAAE;GAAE,CAAC;GAC/D,IAAI,CAACiD,aAAarB,IAAIF,KAAK1B,EAAE,GAC3ByD,IAAI/B,KAAK1B,EAAE;EAEf;CACF;CACAyD,IAAI,IAAI;CACR,OAAOD;AACT;AAEA,MAAMI,wBAAwB,EAC5BlC,MACAW,WACAwB,uBAK0B;CAC1B,MAAMC,QAAmB;EACvB,GAAGpC;EACHwB,UAAU,CAAA;EACVb;CACF;CACA,OAAO;EACL0B,gBAAgBD;EAChBE,aAAa;GACX,MAAM,IAAIC,MAAM,+CAA+C;EACjE;EACAC,sBAAsB;GACpBL,iBAAiBnC,KAAK1B,EAAE;EAC1B;CACF;AACF;AAEA,MAAMmE,oBAAoB1E,SAASmE,oBAAoB;AAEvD,MAAMQ,mBAAkBC,OAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CAAC,MAAA,EAAAC,UAAAH;CAIxB,IAAAI;CAAA,IAAAH,EAAA,OAAAE,OAAA;EAESC,KAAAvC,YAAYsC,KAAK;EAACF,EAAA,KAAAE;EAAAF,EAAA,KAAAG;CAAA,OAAAA,KAAAH,EAAA;CAD1B,MAAA,EAAAlC,UAAA1B,SAAA2B,cACQoC;CAIR,MAAA,CAAAxB,cAAAyB,mBAAwClF,SACtCmF,KACF;CAAE,IAAAC;CAAA,IAAAN,EAAA,OAAAlC,YAAAkC,EAAA,OAAArB,gBAAAqB,EAAA,OAAA5D,SAAA;EAGMkE,KAAA5B,cAAcZ,UAAU1B,SAASuC,YAAY;EAACqB,EAAA,KAAAlC;EAAAkC,EAAA,KAAArB;EAAAqB,EAAA,KAAA5D;EAAA4D,EAAA,KAAAM;CAAA,OAAAA,KAAAN,EAAA;CADtD,MAAAO,eACQD;CAEN,IAAAE;CAAA,IAAAR,EAAA,OAAAS,OAAAC,IAAA,2BAAA,GAAA;EAEqBF,MAAArE,WAAA;GACrBiE,iBAAgBO,SAAA;IACd,MAAAC,OAAa,IAAIpD,IAAImD,IAAI;IACzB,IAAIC,KAAItD,IAAKnB,MAAM,GACjByE,KAAIC,OAAQ1E,MAAM;SAElByE,KAAIjD,IAAKxB,MAAM;IAChB,OACMyE;GAAI,CACZ;EAAC;EACHZ,EAAA,KAAAQ;CAAA,OAAAA,KAAAR,EAAA;CAVD,MAAAJ,iBAAuBY;CAUrB,IAAAM;CAAA,IAAAd,EAAA,OAAAjC,aAAAiC,EAAA,OAAAO,cAAA;EAAA,IAAAQ;EAAA,IAAAf,EAAA,QAAAjC,WAAA;GAGiBgD,MAAA3D,SACfhC,QACEgC,KAAI1B,IACJmE,kBAAkB;IAAAzC;IAAAW;IAAAwB,kBAGEK;GACpB,CAAC,CACH;GAACI,EAAA,MAAAjC;GAAAiC,EAAA,MAAAe;EAAA,OAAAA,KAAAf,EAAA;EARHc,KAAAP,aAAYS,IAAKD,EASjB;EAACf,EAAA,KAAAjC;EAAAiC,EAAA,KAAAO;EAAAP,EAAA,KAAAc;CAAA,OAAAA,KAAAd,EAAA;CAVH,MAAAiB,SAAe5F,gBACbyF,EAUF;CAUa,MAAAC,KAAAhD,UAASE,MAAOF,UAASC;CAEvB,MAAAkD,KAAAD,OAAMzB,MAAMxC,SAAU;CAAC,IAAAmE;CAAA,IAAAnB,EAAA,QAAAiB,OAAAzB,SAAAQ,EAAA,QAAAe,MAAAf,EAAA,QAAAkB,MAAAlB,EAAA,QAAAjC,WAAA;EAVtC,MAAAqD,YAA6B;GAAA1F,IACvB;GAAUC,cACA;GAAIC,MACZ;GAAEC,MACF;GAAMC,QACJ;GAAWC,WACRgC,UAASC;GAAIhC,SACf+B,UAASE;GAAIhC,WACX8E;GAA6BnE,OACjC;GAAEyB,aACI6C;GAAuB5C,aACvB;GAAKM,UACRqC,OAAMzB;GAAMzB;EAExB;EAGYoD,WAAMC;EAASpB,EAAA,MAAAiB,OAAAzB;EAAAQ,EAAA,MAAAe;EAAAf,EAAA,MAAAkB;EAAAlB,EAAA,MAAAjC;EAAAiC,EAAA,MAAAmB;CAAA,OAAAA,KAAAnB,EAAA;CAAA,IAAAqB;CAAA,IAAArB,EAAA,QAAAiB,OAAAzE,OAAAwD,EAAA,QAAAmB,IAAA;EADpBE,KAAA;GAAA5B,UACK0B;GAAezB,OAClBuB,OAAMzE;GAAIoD,gBACD0B;EAGlB;EAACtB,EAAA,MAAAiB,OAAAzE;EAAAwD,EAAA,MAAAmB;EAAAnB,EAAA,MAAAqB;CAAA,OAAAA,KAAArB,EAAA;CAAA,OANMqB;AAMN;AAGH,MAAaE,eAAepG,SAAS2E,eAAe;AArE5B,SAAAO,QAAA;CAAA,uBAWd,IAAI7C,IAAI;AAAC;AAXK,SAAA8D,SAAA,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-o11y",
3
- "version": "0.0.33",
3
+ "version": "0.0.34",
4
4
  "description": "Observability primitives for @assistant-ui",
5
5
  "keywords": [
6
6
  "observability",
@@ -29,8 +29,8 @@
29
29
  "sideEffects": false,
30
30
  "dependencies": {
31
31
  "@radix-ui/react-primitive": "^2.1.10",
32
- "@assistant-ui/store": "0.3.3",
33
- "@assistant-ui/tap": "0.9.9"
32
+ "@assistant-ui/store": "0.3.4",
33
+ "@assistant-ui/tap": "0.9.10"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@types/react": "*",
@@ -47,7 +47,7 @@
47
47
  "react": "^19.2.8",
48
48
  "react-dom": "^19.2.8",
49
49
  "vitest": "^4.1.10",
50
- "@assistant-ui/x-buildutils": "0.0.20"
50
+ "@assistant-ui/x-buildutils": "0.0.21"
51
51
  },
52
52
  "publishConfig": {
53
53
  "access": "public",
@@ -1,5 +1,5 @@
1
1
  import type { FC, PropsWithChildren } from "react";
2
- import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
2
+ import { useAui, AuiConfig, AuiProvider, Derived } from "@assistant-ui/store";
3
3
 
4
4
  export const SpanByIndexProvider: FC<PropsWithChildren<{ index: number }>> = ({
5
5
  index,
@@ -7,13 +7,16 @@ export const SpanByIndexProvider: FC<PropsWithChildren<{ index: number }>> = ({
7
7
  }) => {
8
8
  const parentAui = useAui();
9
9
 
10
- const aui = useAui({
10
+ const config = AuiConfig({
11
11
  span: Derived({
12
12
  source: "span",
13
13
  query: { index },
14
14
  get: () => parentAui.span.child({ index }),
15
15
  }),
16
16
  });
17
-
18
- return <AuiProvider value={aui}>{children}</AuiProvider>;
17
+ return (
18
+ <AuiProvider extends={parentAui} config={config}>
19
+ {children}
20
+ </AuiProvider>
21
+ );
19
22
  };
@@ -0,0 +1,106 @@
1
+ import { renderToStaticMarkup } from "react-dom/server";
2
+ import { describe, expect, it } from "vitest";
3
+ import { AuiProvider, useAui } from "@assistant-ui/store";
4
+ import * as SpanPrimitive from "../primitives/span";
5
+ import { SpanResource, type SpanData } from "./SpanResource";
6
+
7
+ const createSpan = (id: string, parentSpanId: string | null): SpanData => ({
8
+ id,
9
+ parentSpanId,
10
+ name: id,
11
+ type: "test",
12
+ status: "completed",
13
+ startedAt: 0,
14
+ endedAt: 1,
15
+ latencyMs: 1,
16
+ });
17
+
18
+ const SpanFixture = ({ spans }: { spans: SpanData[] }) => {
19
+ const aui = useAui({ span: SpanResource({ spans }) });
20
+
21
+ return (
22
+ <AuiProvider value={aui}>
23
+ <SpanPrimitive.Children>
24
+ {({ span }) => (
25
+ <span
26
+ data-span-id={span.id}
27
+ data-parent-span-id={span.parentSpanId ?? "root"}
28
+ data-span-depth={span.depth}
29
+ />
30
+ )}
31
+ </SpanPrimitive.Children>
32
+ </AuiProvider>
33
+ );
34
+ };
35
+
36
+ const renderSpans = (spans: SpanData[]) =>
37
+ renderToStaticMarkup(<SpanFixture spans={spans} />);
38
+
39
+ describe("SpanResource", () => {
40
+ it("preserves valid parent hierarchies", () => {
41
+ const html = renderSpans([
42
+ createSpan("root", null),
43
+ createSpan("child", "root"),
44
+ ]);
45
+
46
+ expect(html).toContain(
47
+ 'data-span-id="root" data-parent-span-id="root" data-span-depth="0"',
48
+ );
49
+ expect(html).toContain(
50
+ 'data-span-id="child" data-parent-span-id="root" data-span-depth="1"',
51
+ );
52
+ });
53
+
54
+ it("renders cyclic parent hierarchies without recursing indefinitely", () => {
55
+ const html = renderSpans([
56
+ createSpan("first", "second"),
57
+ createSpan("second", "first"),
58
+ ]);
59
+
60
+ expect(html.match(/data-span-id=/g)).toHaveLength(2);
61
+ expect(html).toContain(
62
+ 'data-span-id="first" data-parent-span-id="root" data-span-depth="0"',
63
+ );
64
+ expect(html).toContain(
65
+ 'data-span-id="second" data-parent-span-id="first" data-span-depth="1"',
66
+ );
67
+ });
68
+
69
+ it("renders every span when a parent chain enters a longer cycle", () => {
70
+ const html = renderSpans([
71
+ createSpan("branch", "first"),
72
+ createSpan("first", "second"),
73
+ createSpan("second", "third"),
74
+ createSpan("third", "first"),
75
+ ]);
76
+
77
+ expect(html.match(/data-span-id=/g)).toHaveLength(4);
78
+ expect(html).toContain(
79
+ 'data-span-id="first" data-parent-span-id="root" data-span-depth="0"',
80
+ );
81
+ expect(html).toContain(
82
+ 'data-span-id="third" data-parent-span-id="first" data-span-depth="1"',
83
+ );
84
+ expect(html).toContain(
85
+ 'data-span-id="second" data-parent-span-id="third" data-span-depth="2"',
86
+ );
87
+ expect(html).toContain(
88
+ 'data-span-id="branch" data-parent-span-id="first" data-span-depth="1"',
89
+ );
90
+ });
91
+
92
+ it("renders self-parented and orphaned spans as roots", () => {
93
+ const html = renderSpans([
94
+ createSpan("self", "self"),
95
+ createSpan("orphan", "missing"),
96
+ ]);
97
+
98
+ expect(html.match(/data-span-id=/g)).toHaveLength(2);
99
+ expect(html).toContain(
100
+ 'data-span-id="self" data-parent-span-id="root" data-span-depth="0"',
101
+ );
102
+ expect(html).toContain(
103
+ 'data-span-id="orphan" data-parent-span-id="root" data-span-depth="0"',
104
+ );
105
+ });
106
+ });
@@ -18,25 +18,76 @@ export type SpanData = {
18
18
 
19
19
  function calculateDepth(
20
20
  spanId: string,
21
- spanMap: Map<string, SpanData>,
21
+ parents: Map<string, string | null>,
22
22
  cache: Map<string, number>,
23
23
  ): number {
24
24
  const cached = cache.get(spanId);
25
25
  if (cached !== undefined) return cached;
26
26
 
27
- const span = spanMap.get(spanId);
28
- if (!span?.parentSpanId) {
29
- cache.set(spanId, 0);
30
- return 0;
27
+ const path: string[] = [];
28
+ let current: string | null = spanId;
29
+ let depth = -1;
30
+
31
+ while (current !== null) {
32
+ const currentDepth = cache.get(current);
33
+ if (currentDepth !== undefined) {
34
+ depth = currentDepth;
35
+ break;
36
+ }
37
+
38
+ path.push(current);
39
+ current = parents.get(current) ?? null;
40
+ }
41
+
42
+ for (let index = path.length - 1; index >= 0; index--) {
43
+ depth += 1;
44
+ cache.set(path[index]!, depth);
31
45
  }
32
46
 
33
- const depth = calculateDepth(span.parentSpanId, spanMap, cache) + 1;
34
- cache.set(spanId, depth);
35
47
  return depth;
36
48
  }
37
49
 
50
+ function normalizeSpanParents(
51
+ spanMap: Map<string, SpanData>,
52
+ ): Map<string, string | null> {
53
+ const parents = new Map<string, string | null>();
54
+ for (const span of spanMap.values()) {
55
+ parents.set(
56
+ span.id,
57
+ span.parentSpanId && spanMap.has(span.parentSpanId)
58
+ ? span.parentSpanId
59
+ : null,
60
+ );
61
+ }
62
+
63
+ const resolved = new Set<string>();
64
+ for (const spanId of spanMap.keys()) {
65
+ if (resolved.has(spanId)) continue;
66
+
67
+ const path: string[] = [];
68
+ const pathIds = new Set<string>();
69
+ let current: string | null = spanId;
70
+
71
+ while (current !== null && !resolved.has(current)) {
72
+ if (pathIds.has(current)) {
73
+ parents.set(current, null);
74
+ break;
75
+ }
76
+
77
+ path.push(current);
78
+ pathIds.add(current);
79
+ current = parents.get(current) ?? null;
80
+ }
81
+
82
+ for (const id of path) resolved.add(id);
83
+ }
84
+
85
+ return parents;
86
+ }
87
+
38
88
  function enrichSpans(rawSpans: SpanData[]): {
39
89
  allSpans: Map<string, SpanItemState>;
90
+ parents: Map<string, string | null>;
40
91
  timeRange: { min: number; max: number };
41
92
  } {
42
93
  const spanMap = new Map<string, SpanData>();
@@ -44,13 +95,15 @@ function enrichSpans(rawSpans: SpanData[]): {
44
95
  spanMap.set(span.id, span);
45
96
  }
46
97
 
98
+ const parents = normalizeSpanParents(spanMap);
47
99
  const depthCache = new Map<string, number>();
48
100
  const childrenCount = new Map<string, number>();
49
101
  for (const span of rawSpans) {
50
- if (span.parentSpanId) {
102
+ const parentSpanId = parents.get(span.id);
103
+ if (parentSpanId) {
51
104
  childrenCount.set(
52
- span.parentSpanId,
53
- (childrenCount.get(span.parentSpanId) ?? 0) + 1,
105
+ parentSpanId,
106
+ (childrenCount.get(parentSpanId) ?? 0) + 1,
54
107
  );
55
108
  }
56
109
  }
@@ -60,9 +113,15 @@ function enrichSpans(rawSpans: SpanData[]): {
60
113
  let max = -Infinity;
61
114
 
62
115
  for (const span of rawSpans) {
63
- const depth = calculateDepth(span.id, spanMap, depthCache);
116
+ const depth = calculateDepth(span.id, parents, depthCache);
64
117
  const hasChildren = (childrenCount.get(span.id) ?? 0) > 0;
65
- allSpans.set(span.id, { ...span, depth, hasChildren, isCollapsed: false });
118
+ allSpans.set(span.id, {
119
+ ...span,
120
+ parentSpanId: parents.get(span.id) ?? null,
121
+ depth,
122
+ hasChildren,
123
+ isCollapsed: false,
124
+ });
66
125
 
67
126
  if (span.startedAt < min) min = span.startedAt;
68
127
  const end = span.endedAt ?? Date.now();
@@ -73,16 +132,17 @@ function enrichSpans(rawSpans: SpanData[]): {
73
132
  if (max === -Infinity) max = Date.now();
74
133
  if (max === min) max = min + 100;
75
134
 
76
- return { allSpans, timeRange: { min, max } };
135
+ return { allSpans, parents, timeRange: { min, max } };
77
136
  }
78
137
 
79
138
  function buildFlatList(
80
139
  allSpans: Map<string, SpanItemState>,
140
+ parents: Map<string, string | null>,
81
141
  collapsedIds: Set<string>,
82
142
  ): SpanItemState[] {
83
143
  const children = new Map<string | null, SpanItemState[]>();
84
144
  for (const span of allSpans.values()) {
85
- const parent = span.parentSpanId ?? null;
145
+ const parent = parents.get(span.id) ?? null;
86
146
  let group = children.get(parent);
87
147
  if (!group) {
88
148
  group = [];
@@ -142,15 +202,18 @@ const useSpanResource = ({
142
202
  }: {
143
203
  spans: SpanData[];
144
204
  }): ClientOutput<"span"> => {
145
- const { allSpans, timeRange } = useMemo(() => enrichSpans(spans), [spans]);
205
+ const { allSpans, parents, timeRange } = useMemo(
206
+ () => enrichSpans(spans),
207
+ [spans],
208
+ );
146
209
 
147
210
  const [collapsedIds, setCollapsedIds] = useState<Set<string>>(
148
211
  () => new Set(),
149
212
  );
150
213
 
151
214
  const visibleSpans = useMemo(
152
- () => buildFlatList(allSpans, collapsedIds),
153
- [allSpans, collapsedIds],
215
+ () => buildFlatList(allSpans, parents, collapsedIds),
216
+ [allSpans, parents, collapsedIds],
154
217
  );
155
218
 
156
219
  const toggleCollapse = (spanId: string) => {