react-native-nitro-tracing 0.6.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.
Files changed (233) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/NitroTracing.podspec +32 -0
  4. package/README.md +75 -0
  5. package/android/CMakeLists.txt +49 -0
  6. package/android/build.gradle +133 -0
  7. package/android/fix-prefab.gradle +51 -0
  8. package/android/gradle.properties +5 -0
  9. package/android/proguard-rules.pro +5 -0
  10. package/android/src/main/AndroidManifest.xml +2 -0
  11. package/android/src/main/cpp/cpp-adapter.cpp +47 -0
  12. package/android/src/main/java/com/margelo/nitro/nitrotracing/HermesSamplingGuard.kt +43 -0
  13. package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingFrameSource.kt +36 -0
  14. package/android/src/main/java/com/margelo/nitro/nitrotracing/NitroTracingPackage.kt +31 -0
  15. package/client.ts +15 -0
  16. package/cpp/HermesSampling.cpp +26 -0
  17. package/cpp/HermesSampling.hpp +10 -0
  18. package/cpp/HybridRecording.cpp +66 -0
  19. package/cpp/HybridRecording.hpp +30 -0
  20. package/cpp/HybridTraceSpan.cpp +10 -0
  21. package/cpp/HybridTraceSpan.hpp +17 -0
  22. package/cpp/HybridTracing.cpp +10 -0
  23. package/cpp/HybridTracing.hpp +10 -0
  24. package/cpp/NativeConversions.cpp +77 -0
  25. package/cpp/NativeConversions.hpp +15 -0
  26. package/cpp/core/NativeSampler.cpp +152 -0
  27. package/cpp/core/NativeSampler.hpp +56 -0
  28. package/cpp/core/Recorder.cpp +218 -0
  29. package/cpp/core/Recorder.hpp +60 -0
  30. package/cpp/core/Retention.cpp +26 -0
  31. package/cpp/core/Retention.hpp +7 -0
  32. package/cpp/core/TraceJson.cpp +214 -0
  33. package/cpp/core/TraceJson.hpp +7 -0
  34. package/cpp/core/TraceTypes.hpp +62 -0
  35. package/expo.ts +2 -0
  36. package/ios/NitroTracingFrameSource.mm +35 -0
  37. package/lib/commonjs/client.cjs +767 -0
  38. package/lib/commonjs/client.cjs.map +7 -0
  39. package/lib/commonjs/expo.cjs +88 -0
  40. package/lib/commonjs/expo.cjs.map +7 -0
  41. package/lib/commonjs/index.cjs +28 -0
  42. package/lib/commonjs/index.cjs.map +7 -0
  43. package/lib/commonjs/performance.cjs +112 -0
  44. package/lib/commonjs/performance.cjs.map +7 -0
  45. package/lib/commonjs/plugins.cjs +477 -0
  46. package/lib/commonjs/plugins.cjs.map +7 -0
  47. package/lib/commonjs/react.cjs +3833 -0
  48. package/lib/commonjs/react.cjs.map +7 -0
  49. package/lib/commonjs/release-profiler.cjs +142 -0
  50. package/lib/commonjs/release-profiler.cjs.map +7 -0
  51. package/lib/commonjs/sentry.cjs +188 -0
  52. package/lib/commonjs/sentry.cjs.map +7 -0
  53. package/lib/module/client.mjs +768 -0
  54. package/lib/module/client.mjs.map +7 -0
  55. package/lib/module/expo.mjs +62 -0
  56. package/lib/module/expo.mjs.map +7 -0
  57. package/lib/module/index.mjs +7 -0
  58. package/lib/module/index.mjs.map +7 -0
  59. package/lib/module/performance.mjs +89 -0
  60. package/lib/module/performance.mjs.map +7 -0
  61. package/lib/module/plugins.mjs +461 -0
  62. package/lib/module/plugins.mjs.map +7 -0
  63. package/lib/module/react.mjs +3851 -0
  64. package/lib/module/react.mjs.map +7 -0
  65. package/lib/module/release-profiler.mjs +137 -0
  66. package/lib/module/release-profiler.mjs.map +7 -0
  67. package/lib/module/sentry.mjs +165 -0
  68. package/lib/module/sentry.mjs.map +7 -0
  69. package/lib/typescript/client.d.ts +4 -0
  70. package/lib/typescript/expo.d.ts +2 -0
  71. package/lib/typescript/performance.d.ts +2 -0
  72. package/lib/typescript/plugins.d.ts +12 -0
  73. package/lib/typescript/react.d.ts +5 -0
  74. package/lib/typescript/release-profiler.d.ts +2 -0
  75. package/lib/typescript/sentry.d.ts +2 -0
  76. package/lib/typescript/src/client/TracingSink.d.ts +10 -0
  77. package/lib/typescript/src/client/createTraceClient.d.ts +82 -0
  78. package/lib/typescript/src/client/playground.d.ts +12 -0
  79. package/lib/typescript/src/expo/ExpoAdapter.d.ts +15 -0
  80. package/lib/typescript/src/expo/index.d.ts +2 -0
  81. package/lib/typescript/src/index.d.ts +6 -0
  82. package/lib/typescript/src/plugins/TracePlugin.d.ts +22 -0
  83. package/lib/typescript/src/plugins/errors.d.ts +15 -0
  84. package/lib/typescript/src/plugins/nativeMetrics.d.ts +13 -0
  85. package/lib/typescript/src/plugins/navigation.d.ts +29 -0
  86. package/lib/typescript/src/plugins/network.d.ts +23 -0
  87. package/lib/typescript/src/plugins/orderSpans.d.ts +3 -0
  88. package/lib/typescript/src/plugins/performance.d.ts +13 -0
  89. package/lib/typescript/src/plugins/releaseProfiler.d.ts +25 -0
  90. package/lib/typescript/src/plugins/runtimeMetrics.d.ts +17 -0
  91. package/lib/typescript/src/plugins/sentry.d.ts +15 -0
  92. package/lib/typescript/src/plugins/startPlugins.d.ts +4 -0
  93. package/lib/typescript/src/react/DetailView.d.ts +11 -0
  94. package/lib/typescript/src/react/ExplorerFilters.d.ts +9 -0
  95. package/lib/typescript/src/react/ExplorerView.d.ts +9 -0
  96. package/lib/typescript/src/react/InspectorControls.d.ts +229 -0
  97. package/lib/typescript/src/react/InspectorHeader.d.ts +8 -0
  98. package/lib/typescript/src/react/InspectorSheet.d.ts +16 -0
  99. package/lib/typescript/src/react/MetricsView.d.ts +7 -0
  100. package/lib/typescript/src/react/SummaryView.d.ts +11 -0
  101. package/lib/typescript/src/react/TimelineView.d.ts +8 -0
  102. package/lib/typescript/src/react/TraceInspector.d.ts +11 -0
  103. package/lib/typescript/src/react/TraceOverlay.d.ts +17 -0
  104. package/lib/typescript/src/react/explorerModel.d.ts +38 -0
  105. package/lib/typescript/src/react/labels.d.ts +203 -0
  106. package/lib/typescript/src/react/native.d.ts +42 -0
  107. package/lib/typescript/src/react/readSnapshot.d.ts +4 -0
  108. package/lib/typescript/src/react/store.d.ts +9 -0
  109. package/lib/typescript/src/react/topics.d.ts +80 -0
  110. package/lib/typescript/src/react/useAppReadyMetric.d.ts +3 -0
  111. package/lib/typescript/src/react/useLiveRecording.d.ts +4 -0
  112. package/lib/typescript/src/react/useTraceViewer.d.ts +140 -0
  113. package/lib/typescript/src/react/viewerModel.d.ts +59 -0
  114. package/lib/typescript/src/specs/Recording.nitro.d.ts +31 -0
  115. package/lib/typescript/src/specs/TraceSpan.nitro.d.ts +14 -0
  116. package/lib/typescript/src/specs/Tracing.nitro.d.ts +17 -0
  117. package/lib/typescript/src/types/CompletedSpanOptions.d.ts +15 -0
  118. package/lib/typescript/src/types/EventContext.d.ts +8 -0
  119. package/lib/typescript/src/types/MarkEvent.d.ts +4 -0
  120. package/lib/typescript/src/types/MarkOptions.d.ts +6 -0
  121. package/lib/typescript/src/types/MetricEvent.d.ts +8 -0
  122. package/lib/typescript/src/types/MetricOptions.d.ts +10 -0
  123. package/lib/typescript/src/types/NativeSamplingOptions.d.ts +7 -0
  124. package/lib/typescript/src/types/ReadOptions.d.ts +7 -0
  125. package/lib/typescript/src/types/RecordingOptions.d.ts +11 -0
  126. package/lib/typescript/src/types/RecordingStats.d.ts +25 -0
  127. package/lib/typescript/src/types/SpanEvent.d.ts +13 -0
  128. package/lib/typescript/src/types/SpanOptions.d.ts +6 -0
  129. package/lib/typescript/src/types/SpanOutcome.d.ts +2 -0
  130. package/lib/typescript/src/types/TraceAttribute.d.ts +7 -0
  131. package/lib/typescript/src/types/TraceAttributes.d.ts +3 -0
  132. package/lib/typescript/src/types/TraceContext.d.ts +10 -0
  133. package/lib/typescript/src/types/TracePage.d.ts +18 -0
  134. package/lib/typescript/src/types.d.ts +17 -0
  135. package/nitro.json +20 -0
  136. package/nitrogen/generated/.gitattributes +1 -0
  137. package/nitrogen/generated/android/NitroTracing+autolinking.cmake +83 -0
  138. package/nitrogen/generated/android/NitroTracing+autolinking.gradle +27 -0
  139. package/nitrogen/generated/android/NitroTracingOnLoad.cpp +49 -0
  140. package/nitrogen/generated/android/NitroTracingOnLoad.hpp +34 -0
  141. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrotracing/NitroTracingOnLoad.kt +35 -0
  142. package/nitrogen/generated/ios/NitroTracing+autolinking.rb +62 -0
  143. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.cpp +17 -0
  144. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Bridge.hpp +27 -0
  145. package/nitrogen/generated/ios/NitroTracing-Swift-Cxx-Umbrella.hpp +38 -0
  146. package/nitrogen/generated/ios/NitroTracingAutolinking.mm +35 -0
  147. package/nitrogen/generated/ios/NitroTracingAutolinking.swift +16 -0
  148. package/nitrogen/generated/shared/c++/CompletedSpanOptions.hpp +122 -0
  149. package/nitrogen/generated/shared/c++/HybridRecordingSpec.cpp +31 -0
  150. package/nitrogen/generated/shared/c++/HybridRecordingSpec.hpp +100 -0
  151. package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.cpp +23 -0
  152. package/nitrogen/generated/shared/c++/HybridTraceSpanSpec.hpp +65 -0
  153. package/nitrogen/generated/shared/c++/HybridTracingSpec.cpp +22 -0
  154. package/nitrogen/generated/shared/c++/HybridTracingSpec.hpp +68 -0
  155. package/nitrogen/generated/shared/c++/MarkEvent.hpp +102 -0
  156. package/nitrogen/generated/shared/c++/MarkOptions.hpp +99 -0
  157. package/nitrogen/generated/shared/c++/MetricEvent.hpp +110 -0
  158. package/nitrogen/generated/shared/c++/MetricOptions.hpp +107 -0
  159. package/nitrogen/generated/shared/c++/NativeSamplingOptions.hpp +87 -0
  160. package/nitrogen/generated/shared/c++/ReadOptions.hpp +87 -0
  161. package/nitrogen/generated/shared/c++/RecordingOptions.hpp +95 -0
  162. package/nitrogen/generated/shared/c++/RecordingStats.hpp +123 -0
  163. package/nitrogen/generated/shared/c++/SpanEvent.hpp +121 -0
  164. package/nitrogen/generated/shared/c++/SpanOptions.hpp +99 -0
  165. package/nitrogen/generated/shared/c++/SpanOutcome.hpp +84 -0
  166. package/nitrogen/generated/shared/c++/TraceAttribute.hpp +87 -0
  167. package/nitrogen/generated/shared/c++/TracePage.hpp +111 -0
  168. package/package.json +236 -0
  169. package/performance.ts +2 -0
  170. package/plugins.ts +24 -0
  171. package/react-native.config.js +16 -0
  172. package/react.ts +10 -0
  173. package/release-profiler.ts +6 -0
  174. package/sentry.ts +2 -0
  175. package/src/client/TracingSink.ts +25 -0
  176. package/src/client/createTraceClient.ts +439 -0
  177. package/src/client/playground.ts +143 -0
  178. package/src/expo/ExpoAdapter.ts +71 -0
  179. package/src/expo/index.ts +2 -0
  180. package/src/index.ts +8 -0
  181. package/src/plugins/TracePlugin.ts +22 -0
  182. package/src/plugins/errors.ts +69 -0
  183. package/src/plugins/nativeMetrics.ts +26 -0
  184. package/src/plugins/navigation.ts +111 -0
  185. package/src/plugins/network.ts +178 -0
  186. package/src/plugins/orderSpans.ts +22 -0
  187. package/src/plugins/performance.ts +120 -0
  188. package/src/plugins/releaseProfiler.ts +123 -0
  189. package/src/plugins/runtimeMetrics.ts +131 -0
  190. package/src/plugins/sentry.ts +176 -0
  191. package/src/plugins/startPlugins.ts +58 -0
  192. package/src/react/DetailView.tsx +514 -0
  193. package/src/react/ExplorerFilters.tsx +151 -0
  194. package/src/react/ExplorerView.tsx +243 -0
  195. package/src/react/InspectorControls.tsx +651 -0
  196. package/src/react/InspectorHeader.tsx +171 -0
  197. package/src/react/InspectorSheet.tsx +89 -0
  198. package/src/react/MetricsView.tsx +182 -0
  199. package/src/react/SummaryView.tsx +121 -0
  200. package/src/react/TimelineView.tsx +87 -0
  201. package/src/react/TraceInspector.tsx +196 -0
  202. package/src/react/TraceOverlay.tsx +585 -0
  203. package/src/react/explorerModel.ts +285 -0
  204. package/src/react/labels.ts +234 -0
  205. package/src/react/native.tsx +159 -0
  206. package/src/react/readSnapshot.ts +58 -0
  207. package/src/react/store.ts +31 -0
  208. package/src/react/topics.ts +427 -0
  209. package/src/react/useAppReadyMetric.ts +10 -0
  210. package/src/react/useLiveRecording.ts +43 -0
  211. package/src/react/useTraceViewer.ts +333 -0
  212. package/src/react/viewerModel.ts +172 -0
  213. package/src/specs/Recording.nitro.ts +40 -0
  214. package/src/specs/TraceSpan.nitro.ts +14 -0
  215. package/src/specs/Tracing.nitro.ts +14 -0
  216. package/src/types/CompletedSpanOptions.ts +15 -0
  217. package/src/types/EventContext.ts +8 -0
  218. package/src/types/MarkEvent.ts +3 -0
  219. package/src/types/MarkOptions.ts +6 -0
  220. package/src/types/MetricEvent.ts +8 -0
  221. package/src/types/MetricOptions.ts +10 -0
  222. package/src/types/NativeSamplingOptions.ts +7 -0
  223. package/src/types/ReadOptions.ts +7 -0
  224. package/src/types/RecordingOptions.ts +11 -0
  225. package/src/types/RecordingStats.ts +25 -0
  226. package/src/types/SpanEvent.ts +13 -0
  227. package/src/types/SpanOptions.ts +6 -0
  228. package/src/types/SpanOutcome.ts +2 -0
  229. package/src/types/TraceAttribute.ts +7 -0
  230. package/src/types/TraceAttributes.ts +4 -0
  231. package/src/types/TraceContext.ts +10 -0
  232. package/src/types/TracePage.ts +18 -0
  233. package/src/types.ts +17 -0
@@ -0,0 +1,3851 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/react/TraceInspector.tsx
9
+ import { useEffect as useEffect3 } from "react";
10
+ import { Modal as Modal3, ScrollView as ScrollView4, StatusBar, StyleSheet as StyleSheet9 } from "react-native";
11
+ import { SafeAreaProvider as SafeAreaProvider2, SafeAreaView as SafeAreaView3 } from "react-native-safe-area-context";
12
+
13
+ // src/react/store.ts
14
+ import { useSyncExternalStore } from "react";
15
+ function createStore(initial) {
16
+ let value = initial;
17
+ const listeners = /* @__PURE__ */ new Set();
18
+ return {
19
+ get: () => value,
20
+ set(patch) {
21
+ value = { ...value, ...patch };
22
+ for (const listener of listeners) listener();
23
+ },
24
+ subscribe(listener) {
25
+ listeners.add(listener);
26
+ return () => {
27
+ listeners.delete(listener);
28
+ };
29
+ }
30
+ };
31
+ }
32
+ var useStore = (store, select) => useSyncExternalStore(
33
+ store.subscribe,
34
+ () => select(store.get()),
35
+ () => select(store.get())
36
+ );
37
+
38
+ // src/react/readSnapshot.ts
39
+ async function readSnapshot(recording, previous) {
40
+ const page = {
41
+ spans: [],
42
+ marks: [],
43
+ metrics: [],
44
+ nextSequence: previous?.nextSequence ?? 0,
45
+ earliestSequence: 0,
46
+ droppedEvents: 0
47
+ };
48
+ const budget = Math.max(1, Math.ceil(recording.getStats().eventCount / 1e3));
49
+ for (let i = 0; i < budget; i++) {
50
+ const batch = await recording.readEvents({
51
+ afterSequence: page.nextSequence,
52
+ limit: 1e3
53
+ });
54
+ const cursor = page.nextSequence;
55
+ page.spans.push(...batch.spans);
56
+ page.marks.push(...batch.marks);
57
+ page.metrics.push(...batch.metrics);
58
+ page.nextSequence = batch.nextSequence;
59
+ page.earliestSequence = batch.earliestSequence;
60
+ page.droppedEvents = batch.droppedEvents;
61
+ if (batch.nextSequence === cursor || batch.spans.length + batch.marks.length + batch.metrics.length < 1e3)
62
+ break;
63
+ }
64
+ if (!previous) return page;
65
+ if (page.nextSequence === previous.nextSequence && page.earliestSequence === previous.earliestSequence && page.droppedEvents === previous.droppedEvents)
66
+ return previous;
67
+ const keep = (old, added) => {
68
+ const evicted = old.length > 0 && old[0].sequence < page.earliestSequence;
69
+ if (!evicted && added.length === 0) return old;
70
+ return [
71
+ ...evicted ? old.filter((event) => event.sequence >= page.earliestSequence) : old,
72
+ ...added
73
+ ];
74
+ };
75
+ return {
76
+ ...page,
77
+ spans: keep(previous.spans, page.spans),
78
+ marks: keep(previous.marks, page.marks),
79
+ metrics: keep(previous.metrics, page.metrics)
80
+ };
81
+ }
82
+
83
+ // src/react/useTraceViewer.ts
84
+ import {
85
+ useCallback,
86
+ useEffect,
87
+ useDeferredValue,
88
+ useMemo,
89
+ useRef,
90
+ useState,
91
+ useSyncExternalStore as useSyncExternalStore2
92
+ } from "react";
93
+
94
+ // src/react/explorerModel.ts
95
+ var defaultQuery = () => ({
96
+ search: "",
97
+ outcomes: [],
98
+ sources: [],
99
+ minDuration: "",
100
+ from: "",
101
+ to: "",
102
+ correlation: "",
103
+ sort: "newest"
104
+ });
105
+ var isSpan = (event) => "spanId" in event;
106
+ var sourceOf = (event) => event.attributes.find((a) => a.key === "source")?.value || "unknown";
107
+ var compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
108
+ var identity = (event) => isSpan(event) ? event.spanId : `mark:${event.sequence}`;
109
+ var tie = (a, b) => compareText(identity(a), identity(b)) || a.sequence - b.sequence;
110
+ var chronological = (a, b) => a.timestampMs - b.timestampMs || tie(a, b);
111
+ var durationOf = (event) => isSpan(event) ? event.durationMs : 0;
112
+ var endOf = (event) => event.timestampMs + durationOf(event);
113
+ var numeric = (value) => value.trim() && Number.isFinite(Number(value)) ? Number(value) : void 0;
114
+ var overlaps = (start, duration, query) => {
115
+ const from = numeric(query.from);
116
+ const to = numeric(query.to);
117
+ return (from === void 0 || start + duration >= from) && (to === void 0 || start <= to);
118
+ };
119
+ var matchesSearch = (event, search) => !search || [
120
+ event.name,
121
+ event.correlationId,
122
+ isSpan(event) ? event.spanId : "",
123
+ ...event.attributes.flatMap((a) => [a.key, a.value])
124
+ ].some((value) => value.toLowerCase().includes(search.toLowerCase()));
125
+ var forest = (spans) => {
126
+ const sorted = [...spans].sort(chronological);
127
+ const byId = new Map(sorted.map((span) => [span.spanId, span]));
128
+ const children = /* @__PURE__ */ new Map();
129
+ const roots = [];
130
+ for (const span of sorted) {
131
+ const parent = byId.get(span.parentSpanId);
132
+ if (!parent || parent === span) roots.push(span);
133
+ else {
134
+ const siblings = children.get(parent) ?? [];
135
+ siblings.push(span);
136
+ children.set(parent, siblings);
137
+ }
138
+ }
139
+ const visited = /* @__PURE__ */ new Set();
140
+ const rows = [];
141
+ const rowBySpan = /* @__PURE__ */ new Map();
142
+ for (const root of [...roots, ...sorted]) {
143
+ if (visited.has(root)) continue;
144
+ const stack = [{ span: root, depth: 0 }];
145
+ while (stack.length) {
146
+ const next = stack.pop();
147
+ if (visited.has(next.span)) continue;
148
+ visited.add(next.span);
149
+ const row = { ...next, root, children: 0 };
150
+ rows.push(row);
151
+ rowBySpan.set(next.span, row);
152
+ const descendants = children.get(next.span) ?? [];
153
+ for (let i = descendants.length - 1; i >= 0; i--)
154
+ stack.push({
155
+ span: descendants[i],
156
+ depth: next.depth + 1,
157
+ parent: next.span
158
+ });
159
+ }
160
+ }
161
+ for (let i = rows.length - 1; i >= 0; i--) {
162
+ const row = rows[i];
163
+ const parent = row.parent && rowBySpan.get(row.parent);
164
+ if (parent) parent.children += row.children + 1;
165
+ }
166
+ return rows;
167
+ };
168
+ var buildTraces = (page) => {
169
+ const groups = /* @__PURE__ */ new Map();
170
+ for (const event of [...page.spans, ...page.marks]) {
171
+ if (!event.correlationId) continue;
172
+ const key = `correlation:${event.correlationId}`;
173
+ const group = groups.get(key) ?? { id: key, events: [] };
174
+ group.events.push(event);
175
+ groups.set(key, group);
176
+ }
177
+ for (const row of forest(page.spans.filter((span) => !span.correlationId))) {
178
+ const key = `span:${row.root.spanId}`;
179
+ const group = groups.get(key) ?? { id: key, events: [] };
180
+ group.events.push(row.span);
181
+ groups.set(key, group);
182
+ }
183
+ return [...groups.values()].map(({ id, events }) => {
184
+ events.sort(chronological);
185
+ const spans = events.filter(isSpan);
186
+ const start = events[0].timestampMs;
187
+ const end = events.reduce(
188
+ (latest, event) => Math.max(latest, endOf(event)),
189
+ start
190
+ );
191
+ return {
192
+ id,
193
+ name: events[0].name,
194
+ start,
195
+ duration: end - start,
196
+ errors: spans.filter((s2) => s2.outcome === "error").length,
197
+ spans,
198
+ marks: events.filter((e) => !isSpan(e)),
199
+ sources: [...new Set(events.map(sourceOf))].sort(compareText)
200
+ };
201
+ }).sort((a, b) => b.start - a.start || compareText(a.id, b.id));
202
+ };
203
+ var queryEvents = (events, query) => {
204
+ const search = query.search.toLowerCase();
205
+ const minimum = numeric(query.minDuration);
206
+ return events.filter(
207
+ (event) => matchesSearch(event, search) && (!query.correlation || event.correlationId === query.correlation) && (!query.sources.length || query.sources.includes(sourceOf(event))) && (!isSpan(event) || !query.outcomes.length || query.outcomes.includes(event.outcome)) && (!isSpan(event) || minimum === void 0 || event.durationMs >= minimum) && overlaps(event.timestampMs, durationOf(event), query)
208
+ ).sort((a, b) => {
209
+ let order = 0;
210
+ switch (query.sort) {
211
+ case "oldest":
212
+ order = a.timestampMs - b.timestampMs;
213
+ break;
214
+ case "longest":
215
+ order = durationOf(b) - durationOf(a);
216
+ break;
217
+ case "shortest":
218
+ order = durationOf(a) - durationOf(b);
219
+ break;
220
+ case "name":
221
+ order = compareText(a.name, b.name);
222
+ break;
223
+ case "errors":
224
+ order = Number(isSpan(b) && b.outcome === "error") - Number(isSpan(a) && a.outcome === "error");
225
+ break;
226
+ default:
227
+ order = b.timestampMs - a.timestampMs;
228
+ }
229
+ return order || tie(a, b);
230
+ });
231
+ };
232
+ var queryTraces = (traces, query) => {
233
+ const search = query.search.toLowerCase();
234
+ const minimum = numeric(query.minDuration);
235
+ return traces.filter((trace) => {
236
+ const events = [...trace.spans, ...trace.marks];
237
+ return (!search || events.some((event) => matchesSearch(event, search))) && (!query.correlation || events.some((event) => event.correlationId === query.correlation)) && (!query.sources.length || trace.sources.some((source) => query.sources.includes(source))) && (!query.outcomes.length || query.outcomes.some(
238
+ (outcome) => outcome === "error" ? trace.errors > 0 : (outcome === "success" || outcome === "no-error") && trace.errors === 0
239
+ )) && (minimum === void 0 || trace.duration >= minimum) && overlaps(trace.start, trace.duration, query);
240
+ }).sort((a, b) => {
241
+ let order = 0;
242
+ switch (query.sort) {
243
+ case "oldest":
244
+ order = a.start - b.start;
245
+ break;
246
+ case "longest":
247
+ order = b.duration - a.duration;
248
+ break;
249
+ case "shortest":
250
+ order = a.duration - b.duration;
251
+ break;
252
+ case "errors":
253
+ order = b.errors - a.errors;
254
+ break;
255
+ case "count":
256
+ order = b.spans.length - a.spans.length;
257
+ break;
258
+ case "name":
259
+ order = compareText(a.name, b.name);
260
+ break;
261
+ default:
262
+ order = b.start - a.start;
263
+ }
264
+ return order || compareText(a.id, b.id);
265
+ });
266
+ };
267
+ var hierarchyRows = (spans, collapsed) => {
268
+ const rows = forest(spans);
269
+ const start = spans.reduce(
270
+ (min, span) => Math.min(min, span.timestampMs),
271
+ Infinity
272
+ );
273
+ const end = spans.reduce((max, span) => Math.max(max, endOf(span)), -Infinity);
274
+ const duration = Math.max(1, end - start);
275
+ let hiddenBelow = Infinity;
276
+ return rows.filter((row) => {
277
+ if (row.depth > hiddenBelow) return false;
278
+ hiddenBelow = collapsed.has(row.span.spanId) ? row.depth : Infinity;
279
+ return true;
280
+ }).map(({ span, depth, children }) => ({
281
+ span,
282
+ depth,
283
+ children,
284
+ offset: (span.timestampMs - start) / duration,
285
+ width: span.durationMs / duration
286
+ }));
287
+ };
288
+ var traceForSpan = (traces, span) => traces.find(
289
+ (trace) => trace.spans.some(
290
+ (member) => member.spanId === span.spanId && member.sequence === span.sequence
291
+ )
292
+ );
293
+
294
+ // src/react/viewerModel.ts
295
+ var num = (value) => Math.abs(value) >= 100 ? value.toFixed(0) : value.toFixed(1);
296
+ var formatMetric = (value, unit) => {
297
+ if (unit === "ms" && Math.abs(value) >= 1e3)
298
+ return `${(value / 1e3).toFixed(2)} s`;
299
+ if (unit === "MB" && Math.abs(value) >= 1024)
300
+ return `${(value / 1024).toFixed(2)} GB`;
301
+ if (unit === "count") return value.toFixed(0);
302
+ if (unit === "%") return `${num(value)}%`;
303
+ if (unit.endsWith("/s")) return `${num(value)}/s`;
304
+ return `${num(value)} ${unit}`;
305
+ };
306
+ var metricSeries = (metrics) => {
307
+ const groups = /* @__PURE__ */ new Map();
308
+ for (const metric of metrics) {
309
+ const key = `${metric.name} (${metric.unit})`;
310
+ const group = groups.get(key) ?? [];
311
+ group.push(metric);
312
+ groups.set(key, group);
313
+ }
314
+ return [...groups].map(([name, retained]) => {
315
+ const samples = retained.sort((a, b) => a.timestampMs - b.timestampMs).slice(-40);
316
+ const values = retained.map((sample) => sample.value).sort((a, b) => a - b);
317
+ return {
318
+ latest: retained[retained.length - 1]?.value ?? 0,
319
+ median: values.length ? (values[Math.floor((values.length - 1) / 2)] + values[Math.ceil((values.length - 1) / 2)]) / 2 : 0,
320
+ min: values[0] ?? 0,
321
+ peak: values[values.length - 1] ?? 0,
322
+ p95: values[Math.max(0, Math.ceil(values.length * 0.95) - 1)] ?? 0,
323
+ name,
324
+ /** Metric name without the unit suffix used for grouping. */
325
+ metric: retained[0].name,
326
+ unit: retained[0].unit,
327
+ samples,
328
+ retainedCount: retained.length,
329
+ startMs: samples[0]?.timestampMs ?? 0,
330
+ endMs: samples[samples.length - 1]?.timestampMs ?? 0,
331
+ max: Math.max(1, ...samples.map((sample) => Math.abs(sample.value)))
332
+ };
333
+ });
334
+ };
335
+ var recordingMetrics = (page) => metricSeries([
336
+ ...page.metrics,
337
+ ...page.spans.filter((span) => span.outcome === "success" || span.outcome === "error").map((span) => ({
338
+ ...span,
339
+ name: `duration: ${span.name}`,
340
+ value: span.durationMs,
341
+ unit: "ms"
342
+ }))
343
+ ]);
344
+ var operationMetrics = (spans) => {
345
+ const groups = /* @__PURE__ */ new Map();
346
+ for (const span of spans) {
347
+ const group = groups.get(span.name) ?? [];
348
+ group.push(span);
349
+ groups.set(span.name, group);
350
+ }
351
+ return [...groups].map(([name, samples]) => {
352
+ const completed = samples.filter(
353
+ (s2) => s2.outcome === "success" || s2.outcome === "error"
354
+ );
355
+ return {
356
+ name,
357
+ count: samples.length,
358
+ errors: completed.filter((s2) => s2.outcome === "error").length,
359
+ errorRate: completed.length ? completed.filter((s2) => s2.outcome === "error").length / completed.length * 100 : void 0,
360
+ cancelled: samples.filter((s2) => s2.outcome === "cancelled").length,
361
+ interrupted: samples.filter((s2) => s2.outcome === "interrupted").length
362
+ };
363
+ }).sort((a, b) => b.count - a.count);
364
+ };
365
+
366
+ // src/react/topics.ts
367
+ var topicIds = [
368
+ "startup",
369
+ "screens",
370
+ "network",
371
+ "responsiveness",
372
+ "resources",
373
+ "errors",
374
+ "custom"
375
+ ];
376
+ var topicGlyph = {
377
+ startup: { icon: "\u25F4", color: "#ff9500" },
378
+ screens: { icon: "\u25A2", color: "#007aff" },
379
+ network: { icon: "\u21C5", color: "#34c759" },
380
+ responsiveness: { icon: "\u25F7", color: "#5856d6" },
381
+ errors: { icon: "\u25B3", color: "#ff3b30" },
382
+ resources: { icon: "\u25EB", color: "#30b0c7" },
383
+ custom: { icon: "\u2022", color: "#8e8e93" }
384
+ };
385
+ var defaultBudgets = {
386
+ appReadyMs: 2e3,
387
+ screenMs: 1e3,
388
+ requestMs: 1e3,
389
+ stallMs: 250
390
+ };
391
+ var collectorsFor = {
392
+ startup: [],
393
+ screens: ["navigation"],
394
+ network: ["network", "sentry"],
395
+ responsiveness: ["runtime-metrics", "native-metrics", "sentry"],
396
+ resources: ["native-metrics"],
397
+ errors: ["errors"],
398
+ custom: []
399
+ };
400
+ var attribute = (event, key) => event.attributes.find((a) => a.key === key)?.value;
401
+ var isSpan2 = (event) => "spanId" in event;
402
+ var isMetric = (event) => "unit" in event;
403
+ var topicOf = (event) => {
404
+ const source = attribute(event, "source");
405
+ if (event.name.startsWith("app.ready.") || event.name.startsWith("app.start."))
406
+ return "startup";
407
+ if (event.name.startsWith("process.")) return "resources";
408
+ if (event.name.startsWith("ui.")) return "responsiveness";
409
+ if (source === "navigation") return "screens";
410
+ if (source === "network") return "network";
411
+ if (source === "error") return "errors";
412
+ if (source === "js-runtime" || event.name === "js.longtask")
413
+ return "responsiveness";
414
+ return "custom";
415
+ };
416
+ var p95 = (values) => {
417
+ if (!values.length) return void 0;
418
+ const sorted = [...values].sort((a, b) => a - b);
419
+ return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)];
420
+ };
421
+ var over = (kind, topic, title, event, valueMs, budgetMs) => valueMs > budgetMs ? {
422
+ kind,
423
+ topic,
424
+ title,
425
+ event,
426
+ valueMs,
427
+ budgetMs,
428
+ severity: valueMs / budgetMs
429
+ } : void 0;
430
+ var summarizeTopics = (page, collectors, budgets = defaultBudgets) => {
431
+ const byTopic = new Map(topicIds.map((id) => [id, []]));
432
+ for (const event of [...page.spans, ...page.marks, ...page.metrics])
433
+ byTopic.get(topicOf(event)).push(event);
434
+ const issues = [];
435
+ const add = (issue) => issue && issues.push(issue);
436
+ for (const event of [...page.spans, ...page.marks, ...page.metrics]) {
437
+ const topic = topicOf(event);
438
+ if (isMetric(event)) {
439
+ if (topic === "startup")
440
+ add(
441
+ over(
442
+ "slowStartup",
443
+ topic,
444
+ event.name,
445
+ event,
446
+ event.value,
447
+ budgets.appReadyMs
448
+ )
449
+ );
450
+ else if (event.name === "navigation.transition")
451
+ add(
452
+ over(
453
+ "slowScreen",
454
+ topic,
455
+ attribute(event, "screen") ?? event.name,
456
+ event,
457
+ event.value,
458
+ budgets.screenMs
459
+ )
460
+ );
461
+ else if (event.name === "ui.frame_gap.max")
462
+ add(
463
+ over(
464
+ "uiStall",
465
+ topic,
466
+ event.name,
467
+ event,
468
+ event.value,
469
+ budgets.stallMs
470
+ )
471
+ );
472
+ else if (event.name === "ui.frames.frozen" && event.value > 0)
473
+ add({
474
+ kind: "frozenFrames",
475
+ topic,
476
+ title: `${event.value} frozen frames`,
477
+ event,
478
+ severity: 900
479
+ });
480
+ else if (event.name === "js.frame_gap.max" || event.name === "js.event_loop.delay")
481
+ add(
482
+ over("stall", topic, event.name, event, event.value, budgets.stallMs)
483
+ );
484
+ } else if (isSpan2(event)) {
485
+ if (topic === "network")
486
+ add(
487
+ event.outcome === "error" ? {
488
+ kind: "failedRequest",
489
+ topic,
490
+ title: event.name,
491
+ event,
492
+ severity: 1e3
493
+ } : over(
494
+ "slowRequest",
495
+ topic,
496
+ event.name,
497
+ event,
498
+ event.durationMs,
499
+ budgets.requestMs
500
+ )
501
+ );
502
+ else if (event.name === "js.longtask")
503
+ add(
504
+ over(
505
+ "stall",
506
+ topic,
507
+ event.name,
508
+ event,
509
+ event.durationMs,
510
+ budgets.stallMs
511
+ )
512
+ );
513
+ else if (event.outcome === "error")
514
+ add({
515
+ kind: "error",
516
+ topic: "errors",
517
+ title: event.name,
518
+ event,
519
+ severity: 1e3
520
+ });
521
+ } else if (topic === "errors")
522
+ add({
523
+ kind: "error",
524
+ topic,
525
+ title: attribute(event, "message") ?? event.name,
526
+ event,
527
+ severity: 1e3
528
+ });
529
+ }
530
+ issues.sort(
531
+ (a, b) => b.severity - a.severity || b.event.timestampMs - a.event.timestampMs
532
+ );
533
+ const topics = topicIds.map((id) => {
534
+ const events = byTopic.get(id);
535
+ const own = issues.filter((issue) => issue.topic === id);
536
+ const metricValues = (name) => events.filter((e) => isMetric(e) && e.name === name).map((e) => e.value);
537
+ const spans = events.filter(isSpan2);
538
+ const summary = {
539
+ id,
540
+ tracked: id === "custom" || events.length > 0 || collectorsFor[id].some((collector) => collectors.includes(collector)),
541
+ issues: own
542
+ };
543
+ switch (id) {
544
+ case "startup":
545
+ return {
546
+ ...summary,
547
+ count: events.length,
548
+ value: events.filter(isMetric).at(-1)?.value
549
+ };
550
+ case "screens":
551
+ return {
552
+ ...summary,
553
+ count: events.filter((e) => e.name === "navigation.enter").length,
554
+ value: p95(metricValues("navigation.transition"))
555
+ };
556
+ case "network":
557
+ return {
558
+ ...summary,
559
+ count: spans.length,
560
+ value: p95(spans.map((s2) => s2.durationMs))
561
+ };
562
+ case "resources": {
563
+ const latest = (name) => {
564
+ const values = metricValues(name);
565
+ return values.length ? values[values.length - 1].toFixed(0) : "\u2014";
566
+ };
567
+ return {
568
+ ...summary,
569
+ count: events.length,
570
+ values: {
571
+ cpu: latest("process.cpu"),
572
+ memory: latest("process.memory")
573
+ }
574
+ };
575
+ }
576
+ case "responsiveness":
577
+ case "errors":
578
+ return { ...summary, count: own.length };
579
+ default:
580
+ return { ...summary, count: spans.length };
581
+ }
582
+ });
583
+ return { topics, issues };
584
+ };
585
+ var buildTimeline = (page, issues, limit = 500) => {
586
+ const flagged = new Set(issues.map((issue) => issue.event));
587
+ const visits = page.marks.filter(
588
+ (m) => m.name === "navigation.enter" && attribute(m, "source") === "navigation"
589
+ ).sort((a, b) => a.timestampMs - b.timestampMs);
590
+ const visitSet = new Set(visits);
591
+ const events = [
592
+ ...page.spans.filter((s2) => attribute(s2, "source") !== "navigation"),
593
+ ...page.marks.filter((m) => !visitSet.has(m)),
594
+ ...page.metrics.filter((m) => flagged.has(m))
595
+ ].sort((a, b) => b.timestampMs - a.timestampMs || b.sequence - a.sequence).slice(0, limit);
596
+ const groups = /* @__PURE__ */ new Map();
597
+ for (const event of events) {
598
+ let index = -1;
599
+ for (let i = visits.length - 1; i >= 0; i--)
600
+ if (visits[i].timestampMs <= event.timestampMs) {
601
+ index = i;
602
+ break;
603
+ }
604
+ const group = groups.get(index) ?? [];
605
+ group.push(event);
606
+ groups.set(index, group);
607
+ }
608
+ const items = [];
609
+ for (let index = visits.length - 1; index >= -1; index--) {
610
+ const group = groups.get(index) ?? [];
611
+ const visit = visits[index];
612
+ if (!visit && !group.length) continue;
613
+ if (visits.length)
614
+ items.push({
615
+ kind: "screen",
616
+ key: visit ? `screen:${visit.sequence}` : "screen:before",
617
+ name: visit ? attribute(visit, "screen") ?? "" : "",
618
+ timestampMs: visit?.timestampMs ?? 0
619
+ });
620
+ for (const event of group)
621
+ items.push({
622
+ kind: "event",
623
+ key: `${"spanId" in event ? event.spanId : "unit" in event ? "metric" : "mark"}:${event.sequence}`,
624
+ event,
625
+ topic: topicOf(event),
626
+ issue: flagged.has(event)
627
+ });
628
+ }
629
+ return items;
630
+ };
631
+ var currentScreen = (page) => {
632
+ let latest;
633
+ for (const mark of page.marks)
634
+ if (mark.name === "navigation.enter" && (!latest || mark.timestampMs >= latest.timestampMs))
635
+ latest = mark;
636
+ return latest && attribute(latest, "screen");
637
+ };
638
+ var currentVisit = (items) => {
639
+ const start = items.findIndex((item) => item.kind === "screen");
640
+ const header = start >= 0 ? items[start] : void 0;
641
+ const next = items.findIndex((item, i) => i > start && item.kind === "screen");
642
+ const events = items.slice(start + 1, start >= 0 && next >= 0 ? next : void 0).filter((item) => item.kind === "event");
643
+ const count = (test) => events.filter(test).length;
644
+ return {
645
+ screen: header?.kind === "screen" ? header.name : void 0,
646
+ sinceMs: header?.kind === "screen" ? header.timestampMs : void 0,
647
+ events,
648
+ requests: count((item) => item.topic === "network"),
649
+ stalls: count((item) => item.topic === "responsiveness" && item.issue),
650
+ errors: count(
651
+ (item) => item.topic === "errors" || "outcome" in item.event && item.event.outcome === "error"
652
+ ),
653
+ issues: count((item) => item.issue)
654
+ };
655
+ };
656
+ var latestMetric = (page, name) => {
657
+ let latest;
658
+ for (const metric of page.metrics)
659
+ if (metric.name === name && (!latest || metric.timestampMs >= latest.timestampMs))
660
+ latest = metric;
661
+ return latest?.value;
662
+ };
663
+
664
+ // src/react/useTraceViewer.ts
665
+ var empty = () => ({
666
+ spans: [],
667
+ marks: [],
668
+ metrics: [],
669
+ nextSequence: 0,
670
+ earliestSequence: 0,
671
+ droppedEvents: 0
672
+ });
673
+ var useTraceViewer = (client, budgets) => {
674
+ const snapshot = useSyncExternalStore2(
675
+ client.subscribe,
676
+ client.getSnapshot,
677
+ client.getSnapshot
678
+ );
679
+ const [tab, setTab] = useState("overview");
680
+ const [mode, setMode] = useState("traces");
681
+ const [page, setPage] = useState(empty);
682
+ const [telemetry] = useState(() => createStore({}));
683
+ const setStats = (stats) => telemetry.set({ stats });
684
+ const setError = (error) => telemetry.set({ error });
685
+ const pendingPage = useRef(void 0);
686
+ const setPending = (value) => {
687
+ pendingPage.current = value;
688
+ telemetry.set({
689
+ pending: value ? {
690
+ nextSequence: value.nextSequence,
691
+ earliestSequence: value.earliestSequence
692
+ } : void 0
693
+ });
694
+ };
695
+ const [queries, setQueries] = useState({
696
+ traces: defaultQuery(),
697
+ spans: defaultQuery(),
698
+ marks: defaultQuery()
699
+ });
700
+ const [details, setDetails] = useState([]);
701
+ const [paused, setPaused] = useState(false);
702
+ const [holding, setHolding] = useState(false);
703
+ const [metricQuery, setMetricQuery] = useState("");
704
+ const [metricCategory, setMetricCategory] = useState("all");
705
+ const [metricSort, setMetricSort] = useState("name");
706
+ const offsets = useRef({});
707
+ const detailState = useRef({});
708
+ const latestPage = useRef(page);
709
+ const session = useRef(void 0);
710
+ const controls = useRef({ paused, holding, detail: details.length > 0 });
711
+ controls.current = { paused, holding, detail: details.length > 0 };
712
+ latestPage.current = page;
713
+ useEffect(() => {
714
+ if (!snapshot.visible) return;
715
+ let cached;
716
+ let cachedRecording;
717
+ let cancelled = false;
718
+ let timer;
719
+ const refresh = async () => {
720
+ try {
721
+ const recording = client.getRecording();
722
+ if (recording) {
723
+ if (cachedRecording !== recording) {
724
+ cached = void 0;
725
+ cachedRecording = recording;
726
+ }
727
+ const next = await readSnapshot(recording, cached);
728
+ cached = next;
729
+ if (cancelled || recording !== client.getRecording()) return;
730
+ const current = recording.getStats();
731
+ setStats(current);
732
+ if (session.current !== current.sessionId) {
733
+ session.current = current.sessionId;
734
+ setDetails([]);
735
+ setPending(void 0);
736
+ setPage(next);
737
+ setHolding(false);
738
+ offsets.current = {};
739
+ detailState.current = {};
740
+ setQueries(
741
+ (previous) => Object.fromEntries(
742
+ Object.entries(previous).map(([key, q]) => [
743
+ key,
744
+ { ...q, from: "", to: "" }
745
+ ])
746
+ )
747
+ );
748
+ } else if (controls.current.paused || controls.current.holding || controls.current.detail) {
749
+ if (next.nextSequence !== latestPage.current.nextSequence || next.droppedEvents !== latestPage.current.droppedEvents)
750
+ setPending(next);
751
+ } else {
752
+ setPage(next);
753
+ setPending(void 0);
754
+ }
755
+ }
756
+ if (!cancelled) setError(client.getError());
757
+ } catch (failure) {
758
+ if (!cancelled) setError(String(failure));
759
+ } finally {
760
+ if (!cancelled) timer = setTimeout(refresh, 1e3);
761
+ }
762
+ };
763
+ void refresh();
764
+ return () => {
765
+ cancelled = true;
766
+ clearTimeout(timer);
767
+ };
768
+ }, [client, snapshot.visible]);
769
+ const traces = useMemo(() => buildTraces(page), [page.spans, page.marks]);
770
+ const metrics = useMemo(
771
+ () => tab === "explore" ? [] : recordingMetrics(page),
772
+ [page, tab]
773
+ );
774
+ const collectors = snapshot.collectors;
775
+ const { appReadyMs, screenMs, requestMs, stallMs } = {
776
+ ...defaultBudgets,
777
+ ...budgets
778
+ };
779
+ const summary = useMemo(
780
+ () => summarizeTopics(page, collectors ?? [], {
781
+ appReadyMs,
782
+ screenMs,
783
+ requestMs,
784
+ stallMs
785
+ }),
786
+ [page, collectors, appReadyMs, screenMs, requestMs, stallMs]
787
+ );
788
+ const timeline = useMemo(
789
+ () => tab === "timeline" ? buildTimeline(page, summary.issues) : [],
790
+ [page, summary.issues, tab]
791
+ );
792
+ const screen = useMemo(() => currentScreen(page), [page.marks]);
793
+ const operations = useMemo(
794
+ () => tab === "metrics" ? operationMetrics(page.spans) : [],
795
+ [page.spans, tab]
796
+ );
797
+ const query = queries[mode];
798
+ const deferredQuery = useDeferredValue(query);
799
+ const results = useMemo(
800
+ () => mode === "traces" ? queryTraces(traces, deferredQuery) : queryEvents(
801
+ mode === "spans" ? page.spans : page.marks,
802
+ deferredQuery
803
+ ),
804
+ [mode, traces, page.spans, page.marks, deferredQuery]
805
+ );
806
+ const updateQuery = (patch) => {
807
+ setHolding(true);
808
+ setQueries((previous) => ({
809
+ ...previous,
810
+ [mode]: { ...previous[mode], ...patch }
811
+ }));
812
+ offsets.current[mode] = 0;
813
+ };
814
+ const apply = () => {
815
+ const pending = pendingPage.current;
816
+ if (pending) setPage(pending);
817
+ setPending(void 0);
818
+ setHolding(false);
819
+ };
820
+ const act = (work) => {
821
+ void Promise.resolve().then(work).catch((failure) => {
822
+ setError(String(failure));
823
+ client.reportError(failure);
824
+ });
825
+ };
826
+ const detail = details[details.length - 1];
827
+ const open = useCallback((next) => {
828
+ setHolding(true);
829
+ setDetails((previous) => [...previous.slice(-31), next]);
830
+ }, []);
831
+ const showEvents = (mode2, patch) => {
832
+ setTab("explore");
833
+ setMode(mode2);
834
+ setDetails([]);
835
+ setQueries((previous) => ({
836
+ ...previous,
837
+ [mode2]: { ...defaultQuery(), ...patch }
838
+ }));
839
+ offsets.current[mode2] = 0;
840
+ setHolding(true);
841
+ };
842
+ return {
843
+ ...snapshot,
844
+ client,
845
+ tab,
846
+ setTab: (next) => {
847
+ setDetails([]);
848
+ setTab(next);
849
+ },
850
+ mode,
851
+ setMode,
852
+ page,
853
+ telemetry,
854
+ traces,
855
+ topics: summary.topics,
856
+ issues: summary.issues,
857
+ timeline,
858
+ screen,
859
+ results,
860
+ query,
861
+ updateQuery,
862
+ resetQuery: () => {
863
+ setQueries((previous) => ({ ...previous, [mode]: defaultQuery() }));
864
+ offsets.current[mode] = 0;
865
+ },
866
+ loaded: mode === "traces" ? traces.length : mode === "spans" ? page.spans.length : page.marks.length,
867
+ uncorrelated: useMemo(
868
+ () => page.spans.filter((s2) => !s2.correlationId).length + page.marks.filter((s2) => !s2.correlationId).length,
869
+ [page.spans, page.marks]
870
+ ),
871
+ detail,
872
+ detailState,
873
+ details,
874
+ open,
875
+ back: () => setDetails((previous) => previous.slice(0, -1)),
876
+ traceFor: (span) => traceForSpan(traces, span),
877
+ showSpans: (patch) => showEvents("spans", patch),
878
+ showMarks: (patch) => showEvents("marks", patch),
879
+ paused,
880
+ holding,
881
+ setHolding,
882
+ apply,
883
+ togglePause: () => {
884
+ if (paused) apply();
885
+ setPaused(!paused);
886
+ },
887
+ offsets,
888
+ metrics,
889
+ operations,
890
+ metricQuery,
891
+ setMetricQuery,
892
+ metricCategory,
893
+ setMetricCategory,
894
+ metricSort,
895
+ setMetricSort,
896
+ start: () => act(client.start),
897
+ stop: () => act(client.stop),
898
+ export: () => act(client.export),
899
+ sharePerfetto: () => act(() => client.shareTrace("perfetto")),
900
+ toggleProfile: () => act(client.toggleProfile),
901
+ shareProfile: () => act(client.shareProfile),
902
+ playground: () => act(client.playground),
903
+ playgroundResult: snapshot.playground,
904
+ openPlayground: () => {
905
+ setTab("explore");
906
+ setMode("traces");
907
+ setQueries((previous) => ({
908
+ ...previous,
909
+ traces: {
910
+ ...defaultQuery(),
911
+ correlation: snapshot.playground?.correlationId ?? ""
912
+ }
913
+ }));
914
+ }
915
+ };
916
+ };
917
+
918
+ // src/react/InspectorHeader.tsx
919
+ import { Alert, StyleSheet as StyleSheet2, Text as Text2, View as View3 } from "react-native";
920
+
921
+ // src/react/native.tsx
922
+ import { useCallback as useCallback2, useState as useState2 } from "react";
923
+ import {
924
+ ActionSheetIOS,
925
+ Platform,
926
+ View as View2
927
+ } from "react-native";
928
+
929
+ // src/react/InspectorControls.tsx
930
+ import React from "react";
931
+ import {
932
+ Modal,
933
+ Pressable,
934
+ StyleSheet,
935
+ Text,
936
+ TextInput,
937
+ View
938
+ } from "react-native";
939
+ import { SafeAreaView } from "react-native-safe-area-context";
940
+ import { jsx, jsxs } from "react/jsx-runtime";
941
+ var palette = {
942
+ // Light system colors, in the spirit of Xcode Instruments.
943
+ bg: "#ffffff",
944
+ panel: "#f2f2f7",
945
+ line: "#d8d8dc",
946
+ text: "#1d1d1f",
947
+ muted: "#6e6e73",
948
+ accent: "#007aff",
949
+ selected: "#e5efff",
950
+ error: "#ff3b30",
951
+ scrim: "#0003"
952
+ };
953
+ var ui = StyleSheet.create({
954
+ bottomNav: {
955
+ borderTopWidth: StyleSheet.hairlineWidth,
956
+ borderColor: palette.line,
957
+ paddingHorizontal: 10,
958
+ paddingVertical: 8,
959
+ alignItems: "center"
960
+ },
961
+ root: { flex: 1, backgroundColor: palette.bg },
962
+ header: {
963
+ padding: 14,
964
+ gap: 10,
965
+ borderBottomWidth: StyleSheet.hairlineWidth,
966
+ borderColor: palette.line
967
+ },
968
+ row: { flexDirection: "row", alignItems: "center", gap: 8, flexWrap: "wrap" },
969
+ spread: {
970
+ flexDirection: "row",
971
+ alignItems: "center",
972
+ justifyContent: "space-between",
973
+ gap: 10
974
+ },
975
+ content: { padding: 16, gap: 12 },
976
+ title: {
977
+ fontSize: 21,
978
+ fontWeight: "700",
979
+ color: palette.text,
980
+ flexShrink: 1
981
+ },
982
+ heading: { fontSize: 16, fontWeight: "600", color: palette.text },
983
+ text: { fontSize: 14, color: palette.text },
984
+ muted: { fontSize: 12, lineHeight: 18, color: palette.muted },
985
+ error: { color: palette.error, fontSize: 13 },
986
+ card: {
987
+ padding: 14,
988
+ borderRadius: 10,
989
+ backgroundColor: palette.panel,
990
+ gap: 9
991
+ },
992
+ item: {
993
+ padding: 15,
994
+ borderBottomWidth: StyleSheet.hairlineWidth,
995
+ borderColor: palette.line,
996
+ gap: 6
997
+ },
998
+ input: {
999
+ borderWidth: 1,
1000
+ borderColor: palette.line,
1001
+ borderRadius: 8,
1002
+ paddingHorizontal: 12,
1003
+ paddingVertical: 10,
1004
+ color: palette.text,
1005
+ fontSize: 14
1006
+ },
1007
+ button: {
1008
+ paddingHorizontal: 12,
1009
+ paddingVertical: 8,
1010
+ borderRadius: 7,
1011
+ backgroundColor: palette.panel,
1012
+ borderWidth: StyleSheet.hairlineWidth,
1013
+ borderColor: palette.line
1014
+ },
1015
+ selected: { backgroundColor: palette.selected, borderColor: palette.accent },
1016
+ buttonText: { color: palette.text, fontSize: 13, fontWeight: "500" },
1017
+ selectedText: { color: palette.accent },
1018
+ link: { color: palette.accent, fontSize: 13 },
1019
+ chart: { height: 86, flexDirection: "row", gap: 2, alignItems: "flex-end" },
1020
+ bar: { flex: 1, backgroundColor: palette.accent, minHeight: 2 },
1021
+ track: {
1022
+ height: 7,
1023
+ backgroundColor: palette.panel,
1024
+ borderRadius: 3,
1025
+ overflow: "hidden"
1026
+ }
1027
+ });
1028
+ var apple = {
1029
+ grouped: "#f2f2f7",
1030
+ card: "#ffffff",
1031
+ separator: "#c6c6c8",
1032
+ secondary: "#8e8e93",
1033
+ fill: "#e3e3e8"
1034
+ };
1035
+ var hairline = StyleSheet.hairlineWidth;
1036
+ var s = StyleSheet.create({
1037
+ navBar: {
1038
+ flexDirection: "row",
1039
+ alignItems: "center",
1040
+ paddingHorizontal: 8,
1041
+ minHeight: 44,
1042
+ backgroundColor: apple.grouped
1043
+ },
1044
+ navSide: { flex: 1, flexDirection: "row", alignItems: "center", gap: 4 },
1045
+ navTitle: {
1046
+ fontSize: 17,
1047
+ fontWeight: "600",
1048
+ color: palette.text,
1049
+ textAlign: "center"
1050
+ },
1051
+ textButton: { paddingHorizontal: 8, paddingVertical: 10 },
1052
+ textButtonLabel: { fontSize: 17, color: palette.accent },
1053
+ iconLabel: { fontSize: 20, color: palette.accent },
1054
+ section: { marginHorizontal: 16, marginTop: 20 },
1055
+ sectionHeader: {
1056
+ fontSize: 13,
1057
+ color: apple.secondary,
1058
+ textTransform: "uppercase",
1059
+ marginLeft: 16,
1060
+ marginBottom: 6
1061
+ },
1062
+ sectionFooter: {
1063
+ fontSize: 13,
1064
+ color: apple.secondary,
1065
+ marginHorizontal: 16,
1066
+ marginTop: 6,
1067
+ lineHeight: 18
1068
+ },
1069
+ sectionBody: {
1070
+ backgroundColor: apple.card,
1071
+ borderRadius: 10,
1072
+ overflow: "hidden"
1073
+ },
1074
+ row: {
1075
+ flexDirection: "row",
1076
+ alignItems: "center",
1077
+ minHeight: 44,
1078
+ paddingVertical: 8,
1079
+ paddingLeft: 16,
1080
+ paddingRight: 12,
1081
+ backgroundColor: apple.card,
1082
+ gap: 12
1083
+ },
1084
+ rowSeparator: {
1085
+ position: "absolute",
1086
+ left: 16,
1087
+ right: 0,
1088
+ bottom: 0,
1089
+ height: hairline,
1090
+ backgroundColor: apple.separator
1091
+ },
1092
+ rowIcon: { width: 22, textAlign: "center", fontSize: 17 },
1093
+ rowText: { flex: 1, gap: 2 },
1094
+ rowTitle: { fontSize: 16, color: palette.text },
1095
+ rowSubtitle: { fontSize: 12, color: apple.secondary },
1096
+ rowValue: {
1097
+ fontSize: 15,
1098
+ fontWeight: "500",
1099
+ color: apple.secondary,
1100
+ fontVariant: ["tabular-nums"]
1101
+ },
1102
+ chevron: { fontSize: 20, color: "#c4c4c7", marginLeft: -4 },
1103
+ check: { width: 20, fontSize: 17, fontWeight: "600", color: palette.accent },
1104
+ pressed: { backgroundColor: "#e5e5ea" },
1105
+ search: {
1106
+ flexDirection: "row",
1107
+ alignItems: "center",
1108
+ marginHorizontal: 16,
1109
+ marginTop: 8,
1110
+ paddingHorizontal: 8,
1111
+ height: 36,
1112
+ borderRadius: 10,
1113
+ backgroundColor: apple.fill,
1114
+ gap: 6
1115
+ },
1116
+ searchInput: { flex: 1, fontSize: 16, color: palette.text, padding: 0 },
1117
+ segmented: {
1118
+ flexDirection: "row",
1119
+ marginHorizontal: 16,
1120
+ marginTop: 10,
1121
+ padding: 2,
1122
+ borderRadius: 9,
1123
+ backgroundColor: apple.fill
1124
+ },
1125
+ segment: {
1126
+ flex: 1,
1127
+ height: 28,
1128
+ alignItems: "center",
1129
+ justifyContent: "center",
1130
+ paddingHorizontal: 4,
1131
+ borderRadius: 7
1132
+ },
1133
+ segmentOn: {
1134
+ backgroundColor: apple.card,
1135
+ shadowColor: "#000",
1136
+ shadowOpacity: 0.12,
1137
+ shadowRadius: 3,
1138
+ shadowOffset: { width: 0, height: 1 },
1139
+ elevation: 1
1140
+ },
1141
+ segmentText: { fontSize: 13, fontWeight: "500", color: palette.text },
1142
+ segmentTextOn: { fontWeight: "600" },
1143
+ tabBar: {
1144
+ flexDirection: "row",
1145
+ borderTopWidth: hairline,
1146
+ borderColor: apple.separator,
1147
+ backgroundColor: "#f9f9f9",
1148
+ paddingTop: 6,
1149
+ paddingBottom: 4
1150
+ },
1151
+ tab: { flex: 1, alignItems: "center", gap: 2 },
1152
+ tabIcon: {
1153
+ height: 26,
1154
+ fontSize: 20,
1155
+ lineHeight: 26,
1156
+ textAlign: "center",
1157
+ color: apple.secondary
1158
+ },
1159
+ tabLabel: { fontSize: 10, fontWeight: "500", color: apple.secondary },
1160
+ tabOn: { color: palette.accent },
1161
+ banner: {
1162
+ flexDirection: "row",
1163
+ justifyContent: "center",
1164
+ alignSelf: "center",
1165
+ marginTop: 8,
1166
+ paddingHorizontal: 14,
1167
+ paddingVertical: 6,
1168
+ borderRadius: 16,
1169
+ backgroundColor: palette.accent
1170
+ },
1171
+ bannerText: { color: "#fff", fontSize: 13, fontWeight: "600" },
1172
+ scrim: {
1173
+ flex: 1,
1174
+ backgroundColor: palette.scrim,
1175
+ justifyContent: "flex-end"
1176
+ },
1177
+ sheet: { padding: 8, gap: 8 },
1178
+ sheetGroup: {
1179
+ backgroundColor: "rgba(249,249,249,0.97)",
1180
+ borderRadius: 14,
1181
+ overflow: "hidden"
1182
+ },
1183
+ sheetTitle: {
1184
+ fontSize: 13,
1185
+ color: apple.secondary,
1186
+ textAlign: "center",
1187
+ padding: 14,
1188
+ borderBottomWidth: hairline,
1189
+ borderColor: apple.separator
1190
+ },
1191
+ sheetOption: {
1192
+ minHeight: 57,
1193
+ alignItems: "center",
1194
+ justifyContent: "center",
1195
+ borderBottomWidth: hairline,
1196
+ borderColor: apple.separator,
1197
+ paddingHorizontal: 16
1198
+ },
1199
+ sheetOptionText: { fontSize: 20, color: palette.accent },
1200
+ sheetCancel: { fontWeight: "600" }
1201
+ });
1202
+ function NavBar({
1203
+ left,
1204
+ title,
1205
+ right
1206
+ }) {
1207
+ return /* @__PURE__ */ jsxs(View, { style: s.navBar, children: [
1208
+ /* @__PURE__ */ jsx(View, { style: s.navSide, children: left }),
1209
+ /* @__PURE__ */ jsx(Text, { style: s.navTitle, numberOfLines: 1, accessibilityRole: "header", children: title }),
1210
+ /* @__PURE__ */ jsx(View, { style: [s.navSide, { justifyContent: "flex-end" }], children: right })
1211
+ ] });
1212
+ }
1213
+ function TextButton({
1214
+ label,
1215
+ icon,
1216
+ onPress,
1217
+ disabled,
1218
+ bold,
1219
+ accessibilityLabel
1220
+ }) {
1221
+ return /* @__PURE__ */ jsx(
1222
+ Pressable,
1223
+ {
1224
+ accessibilityRole: "button",
1225
+ accessibilityLabel: accessibilityLabel ?? label,
1226
+ disabled,
1227
+ onPress,
1228
+ hitSlop: 6,
1229
+ style: [s.textButton, disabled && { opacity: 0.35 }],
1230
+ children: /* @__PURE__ */ jsx(
1231
+ Text,
1232
+ {
1233
+ style: [
1234
+ icon ? s.iconLabel : s.textButtonLabel,
1235
+ bold && { fontWeight: "600" }
1236
+ ],
1237
+ children: icon ?? label
1238
+ }
1239
+ )
1240
+ }
1241
+ );
1242
+ }
1243
+ function Section({
1244
+ header,
1245
+ footer,
1246
+ children
1247
+ }) {
1248
+ return /* @__PURE__ */ jsxs(View, { style: s.section, children: [
1249
+ header && /* @__PURE__ */ jsx(Text, { style: s.sectionHeader, children: header }),
1250
+ /* @__PURE__ */ jsx(View, { style: s.sectionBody, children }),
1251
+ footer && /* @__PURE__ */ jsx(Text, { style: s.sectionFooter, children: footer })
1252
+ ] });
1253
+ }
1254
+ var Row = React.memo(function Row2({
1255
+ title,
1256
+ subtitle,
1257
+ value,
1258
+ icon,
1259
+ iconColor,
1260
+ tint,
1261
+ onPress,
1262
+ last,
1263
+ checked
1264
+ }) {
1265
+ return /* @__PURE__ */ jsxs(
1266
+ Pressable,
1267
+ {
1268
+ accessibilityRole: checked !== void 0 ? "checkbox" : onPress ? "button" : void 0,
1269
+ accessibilityState: checked !== void 0 ? { checked } : void 0,
1270
+ disabled: !onPress,
1271
+ onPress,
1272
+ style: ({ pressed }) => [s.row, pressed && s.pressed],
1273
+ children: [
1274
+ icon !== void 0 && /* @__PURE__ */ jsx(Text, { style: [s.rowIcon, { color: iconColor ?? palette.accent }], children: icon }),
1275
+ /* @__PURE__ */ jsxs(View, { style: s.rowText, children: [
1276
+ /* @__PURE__ */ jsx(
1277
+ Text,
1278
+ {
1279
+ style: [s.rowTitle, tint && { color: tint }],
1280
+ numberOfLines: 1,
1281
+ ellipsizeMode: "middle",
1282
+ children: title
1283
+ }
1284
+ ),
1285
+ !!subtitle && /* @__PURE__ */ jsx(Text, { style: s.rowSubtitle, numberOfLines: 2, children: subtitle })
1286
+ ] }),
1287
+ !!value && /* @__PURE__ */ jsx(Text, { style: [s.rowValue, tint && { color: tint }], children: value }),
1288
+ checked !== void 0 ? /* @__PURE__ */ jsx(Text, { style: s.check, children: checked ? "\u2713" : "" }) : onPress && /* @__PURE__ */ jsx(Text, { style: s.chevron, children: "\u203A" }),
1289
+ !last && /* @__PURE__ */ jsx(View, { style: s.rowSeparator })
1290
+ ]
1291
+ }
1292
+ );
1293
+ });
1294
+ function SearchField({
1295
+ value,
1296
+ onChange,
1297
+ placeholder
1298
+ }) {
1299
+ return /* @__PURE__ */ jsxs(View, { style: s.search, children: [
1300
+ /* @__PURE__ */ jsx(Text, { style: { color: apple.secondary, fontSize: 20 }, children: "\u2315" }),
1301
+ /* @__PURE__ */ jsx(
1302
+ TextInput,
1303
+ {
1304
+ accessibilityLabel: placeholder,
1305
+ placeholder,
1306
+ placeholderTextColor: apple.secondary,
1307
+ value,
1308
+ onChangeText: onChange,
1309
+ autoCapitalize: "none",
1310
+ autoCorrect: false,
1311
+ clearButtonMode: "while-editing",
1312
+ returnKeyType: "search",
1313
+ style: s.searchInput
1314
+ }
1315
+ )
1316
+ ] });
1317
+ }
1318
+ function Segmented({
1319
+ options,
1320
+ value,
1321
+ onChange
1322
+ }) {
1323
+ return /* @__PURE__ */ jsx(View, { style: s.segmented, accessibilityRole: "tablist", children: options.map((option) => /* @__PURE__ */ jsx(
1324
+ Pressable,
1325
+ {
1326
+ accessibilityRole: "tab",
1327
+ accessibilityState: { selected: option.value === value },
1328
+ onPress: () => onChange(option.value),
1329
+ style: [s.segment, option.value === value && s.segmentOn],
1330
+ children: /* @__PURE__ */ jsx(
1331
+ Text,
1332
+ {
1333
+ style: [s.segmentText, option.value === value && s.segmentTextOn],
1334
+ numberOfLines: 1,
1335
+ adjustsFontSizeToFit: true,
1336
+ minimumFontScale: 0.8,
1337
+ children: option.label
1338
+ }
1339
+ )
1340
+ },
1341
+ option.value
1342
+ )) });
1343
+ }
1344
+ function TabBar({
1345
+ items,
1346
+ value,
1347
+ onChange
1348
+ }) {
1349
+ return /* @__PURE__ */ jsx(View, { style: s.tabBar, accessibilityRole: "tablist", children: items.map((item) => {
1350
+ const on = item.value === value;
1351
+ return /* @__PURE__ */ jsxs(
1352
+ Pressable,
1353
+ {
1354
+ accessibilityRole: "tab",
1355
+ accessibilityState: { selected: on },
1356
+ onPress: () => onChange(item.value),
1357
+ style: s.tab,
1358
+ children: [
1359
+ /* @__PURE__ */ jsx(
1360
+ Text,
1361
+ {
1362
+ style: [
1363
+ s.tabIcon,
1364
+ item.iconSize ? { fontSize: item.iconSize } : void 0,
1365
+ on && s.tabOn
1366
+ ],
1367
+ children: item.icon
1368
+ }
1369
+ ),
1370
+ /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, on && s.tabOn], children: item.label })
1371
+ ]
1372
+ },
1373
+ item.value
1374
+ );
1375
+ }) });
1376
+ }
1377
+ function Banner({
1378
+ label,
1379
+ onPress
1380
+ }) {
1381
+ return /* @__PURE__ */ jsx(Pressable, { accessibilityRole: "button", onPress, style: s.banner, children: /* @__PURE__ */ jsxs(Text, { style: s.bannerText, children: [
1382
+ "\u2191 ",
1383
+ label
1384
+ ] }) });
1385
+ }
1386
+ function ActionSheet({
1387
+ title,
1388
+ options,
1389
+ cancelLabel,
1390
+ close
1391
+ }) {
1392
+ return /* @__PURE__ */ jsx(Modal, { visible: true, transparent: true, animationType: "fade", onRequestClose: close, children: /* @__PURE__ */ jsx(
1393
+ Pressable,
1394
+ {
1395
+ style: s.scrim,
1396
+ onPress: close,
1397
+ accessibilityLabel: cancelLabel,
1398
+ children: /* @__PURE__ */ jsxs(SafeAreaView, { edges: ["bottom"], style: s.sheet, children: [
1399
+ /* @__PURE__ */ jsxs(View, { style: s.sheetGroup, children: [
1400
+ title && /* @__PURE__ */ jsx(Text, { style: s.sheetTitle, children: title }),
1401
+ options.map((option) => /* @__PURE__ */ jsx(
1402
+ Pressable,
1403
+ {
1404
+ accessibilityRole: "button",
1405
+ accessibilityState: {
1406
+ selected: option.selected,
1407
+ disabled: option.disabled
1408
+ },
1409
+ disabled: option.disabled,
1410
+ onPress: () => {
1411
+ close();
1412
+ option.onPress();
1413
+ },
1414
+ style: ({ pressed }) => [s.sheetOption, pressed && s.pressed],
1415
+ children: /* @__PURE__ */ jsxs(
1416
+ Text,
1417
+ {
1418
+ style: [
1419
+ s.sheetOptionText,
1420
+ option.destructive && { color: palette.error },
1421
+ option.disabled && { color: apple.secondary }
1422
+ ],
1423
+ children: [
1424
+ option.selected ? "\u2713 " : "",
1425
+ option.label
1426
+ ]
1427
+ }
1428
+ )
1429
+ },
1430
+ option.label
1431
+ ))
1432
+ ] }),
1433
+ /* @__PURE__ */ jsx(
1434
+ Pressable,
1435
+ {
1436
+ accessibilityRole: "button",
1437
+ onPress: close,
1438
+ style: ({ pressed }) => [
1439
+ s.sheetGroup,
1440
+ s.sheetOption,
1441
+ pressed && s.pressed
1442
+ ],
1443
+ children: /* @__PURE__ */ jsx(Text, { style: [s.sheetOptionText, s.sheetCancel], children: cancelLabel })
1444
+ }
1445
+ )
1446
+ ] })
1447
+ }
1448
+ ) });
1449
+ }
1450
+
1451
+ // src/react/native.tsx
1452
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
1453
+ var NativeTabView;
1454
+ var glass;
1455
+ try {
1456
+ NativeTabView = __require("react-native-bottom-tabs").default;
1457
+ } catch {
1458
+ NativeTabView = void 0;
1459
+ }
1460
+ try {
1461
+ glass = __require("expo-glass-effect");
1462
+ } catch {
1463
+ glass = void 0;
1464
+ }
1465
+ var liquidGlass = (() => {
1466
+ try {
1467
+ return Platform.OS === "ios" && !!glass?.isLiquidGlassAvailable();
1468
+ } catch {
1469
+ return false;
1470
+ }
1471
+ })();
1472
+ function useMenu() {
1473
+ const [config, setConfig] = useState2();
1474
+ const open = useCallback2((next) => {
1475
+ if (Platform.OS !== "ios") return setConfig(next);
1476
+ const enabled = next.options.filter((option) => !option.disabled);
1477
+ const destructive = enabled.findIndex((option) => option.destructive);
1478
+ ActionSheetIOS.showActionSheetWithOptions(
1479
+ {
1480
+ title: next.title,
1481
+ options: [
1482
+ ...enabled.map(
1483
+ (option) => `${option.selected ? "\u2713 " : ""}${option.label}`
1484
+ ),
1485
+ next.cancelLabel
1486
+ ],
1487
+ cancelButtonIndex: enabled.length,
1488
+ destructiveButtonIndex: destructive >= 0 ? destructive : void 0
1489
+ },
1490
+ (index) => enabled[index]?.onPress()
1491
+ );
1492
+ }, []);
1493
+ const element = config ? /* @__PURE__ */ jsx2(ActionSheet, { ...config, close: () => setConfig(void 0) }) : null;
1494
+ return { open, element };
1495
+ }
1496
+ function NativeTabs({
1497
+ tabs: tabs2,
1498
+ value,
1499
+ onChange,
1500
+ tint,
1501
+ fallback
1502
+ }) {
1503
+ const index = Math.max(
1504
+ 0,
1505
+ tabs2.findIndex((tab) => tab.key === value)
1506
+ );
1507
+ if (!NativeTabView || Platform.OS !== "ios")
1508
+ return /* @__PURE__ */ jsx2(Fragment, { children: fallback(tabs2[index]?.render()) });
1509
+ return /* @__PURE__ */ jsx2(
1510
+ NativeTabView,
1511
+ {
1512
+ navigationState: {
1513
+ index,
1514
+ routes: tabs2.map((tab) => ({
1515
+ key: tab.key,
1516
+ title: tab.title,
1517
+ focusedIcon: { sfSymbol: tab.sfSymbol },
1518
+ badge: tab.badge
1519
+ }))
1520
+ },
1521
+ onIndexChange: (next) => onChange(tabs2[next].key),
1522
+ renderScene: ({ route }) => tabs2.find((tab) => tab.key === route.key)?.render() ?? null,
1523
+ getLazy: () => true,
1524
+ getFreezeOnBlur: () => true,
1525
+ getBadge: ({ route }) => tabs2.find((tab) => tab.key === route.key)?.badge,
1526
+ minimizeBehavior: "onScrollDown",
1527
+ tabBarActiveTintColor: tint,
1528
+ hapticFeedbackEnabled: true
1529
+ }
1530
+ );
1531
+ }
1532
+ function Glass({
1533
+ style,
1534
+ fallbackColor,
1535
+ children,
1536
+ interactive
1537
+ }) {
1538
+ if (liquidGlass && glass) {
1539
+ const { GlassView } = glass;
1540
+ return /* @__PURE__ */ jsx2(
1541
+ GlassView,
1542
+ {
1543
+ style,
1544
+ glassEffectStyle: "regular",
1545
+ isInteractive: interactive,
1546
+ children
1547
+ }
1548
+ );
1549
+ }
1550
+ return /* @__PURE__ */ jsx2(View2, { style: [style, { backgroundColor: fallbackColor }], children });
1551
+ }
1552
+
1553
+ // src/react/InspectorHeader.tsx
1554
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1555
+ var titleKey = {
1556
+ trace: "traces",
1557
+ span: "spans",
1558
+ metric: "metrics",
1559
+ mark: "marks"
1560
+ };
1561
+ var tabKey = {
1562
+ overview: "overview",
1563
+ timeline: "timeline",
1564
+ explore: "explore",
1565
+ metrics: "metrics",
1566
+ tools: "tools"
1567
+ };
1568
+ function InspectorHeader({
1569
+ v: viewer,
1570
+ t
1571
+ }) {
1572
+ const live = useStore(viewer.telemetry, (s2) => s2);
1573
+ const v = { ...viewer, ...live };
1574
+ const client = v.client;
1575
+ const menu = useMenu();
1576
+ const canShare = client.canShare && !!v.stats && !v.busy;
1577
+ const startNew = () => Alert.alert(t("replaceTitle"), t("replaceDescription"), [
1578
+ { text: t("cancel"), style: "cancel" },
1579
+ { text: t("start"), style: "destructive", onPress: v.start }
1580
+ ]);
1581
+ const seconds = Math.round((v.stats?.nowMs ?? 0) / 1e3);
1582
+ const lost = [
1583
+ ["lostSpans", v.stats?.droppedSpans],
1584
+ ["lostMarks", v.stats?.droppedMarks],
1585
+ ["lostMetrics", v.stats?.droppedMetrics]
1586
+ ].filter(([, count]) => (count ?? 0) > 0).map(([key, count]) => t(key, { count }));
1587
+ const newEvents = v.pending ? Math.max(0, v.pending.nextSequence - v.page.nextSequence) : 0;
1588
+ return /* @__PURE__ */ jsxs2(View3, { style: styles.root, children: [
1589
+ /* @__PURE__ */ jsx3(
1590
+ NavBar,
1591
+ {
1592
+ left: v.detail ? /* @__PURE__ */ jsx3(TextButton, { label: `\u2039 ${t("back")}`, onPress: v.back }) : /* @__PURE__ */ jsx3(TextButton, { label: t("done"), onPress: client.close, bold: true }),
1593
+ title: t(v.detail ? titleKey[v.detail.kind] : tabKey[v.tab]),
1594
+ right: /* @__PURE__ */ jsxs2(Fragment2, { children: [
1595
+ /* @__PURE__ */ jsx3(
1596
+ TextButton,
1597
+ {
1598
+ icon: "\u21EA",
1599
+ accessibilityLabel: t("share"),
1600
+ onPress: () => menu.open({
1601
+ title: t("shareTitle"),
1602
+ cancelLabel: t("cancel"),
1603
+ options: [
1604
+ {
1605
+ label: t("sharePerfetto"),
1606
+ onPress: v.sharePerfetto,
1607
+ disabled: !canShare
1608
+ },
1609
+ {
1610
+ label: t("exportAll"),
1611
+ onPress: v.export,
1612
+ disabled: !canShare
1613
+ },
1614
+ ...v.profilePath && client.canShareProfile ? [{ label: t("shareProfile"), onPress: v.shareProfile }] : []
1615
+ ]
1616
+ }),
1617
+ disabled: !canShare && !v.profilePath
1618
+ }
1619
+ ),
1620
+ /* @__PURE__ */ jsx3(
1621
+ TextButton,
1622
+ {
1623
+ icon: "\u22EF",
1624
+ accessibilityLabel: t("more"),
1625
+ onPress: () => menu.open({
1626
+ cancelLabel: t("cancel"),
1627
+ options: [
1628
+ v.recording ? { label: t("stop"), onPress: v.stop, destructive: true } : { label: t("start"), onPress: v.start },
1629
+ ...v.recording ? [{ label: t("startNew"), onPress: startNew }] : [],
1630
+ {
1631
+ label: t(v.paused ? "resumeUpdates" : "pauseUpdates"),
1632
+ onPress: v.togglePause
1633
+ },
1634
+ { label: t("tools"), onPress: () => v.setTab("tools") }
1635
+ ]
1636
+ })
1637
+ }
1638
+ )
1639
+ ] })
1640
+ }
1641
+ ),
1642
+ /* @__PURE__ */ jsxs2(Text2, { style: styles.status, numberOfLines: 1, children: [
1643
+ /* @__PURE__ */ jsx3(Text2, { style: { color: v.recording ? palette.error : apple.secondary }, children: v.recording ? "\u25CF" : "\u25CB" }),
1644
+ " ",
1645
+ t(v.recording ? "recording" : "stopped"),
1646
+ " \xB7 ",
1647
+ Math.floor(seconds / 60),
1648
+ ":",
1649
+ String(seconds % 60).padStart(2, "0"),
1650
+ " \xB7 ",
1651
+ v.stats?.eventCount ?? 0,
1652
+ " ",
1653
+ t("events"),
1654
+ v.screen ? ` \xB7 ${v.screen}` : "",
1655
+ v.paused ? ` \xB7 ${t("frozen")}` : ""
1656
+ ] }),
1657
+ lost.length > 0 && /* @__PURE__ */ jsx3(Text2, { style: styles.warning, children: t("lostHistory", { what: lost.join(", ") }) }),
1658
+ v.error && /* @__PURE__ */ jsx3(Text2, { selectable: true, style: styles.warning, numberOfLines: 3, children: v.error }),
1659
+ !v.detail && newEvents > 0 && !v.paused && /* @__PURE__ */ jsx3(
1660
+ Banner,
1661
+ {
1662
+ label: t("newEvents", { count: newEvents }),
1663
+ onPress: v.apply
1664
+ }
1665
+ ),
1666
+ menu.element
1667
+ ] });
1668
+ }
1669
+ var styles = StyleSheet2.create({
1670
+ root: {
1671
+ backgroundColor: apple.grouped,
1672
+ borderBottomWidth: StyleSheet2.hairlineWidth,
1673
+ borderColor: apple.separator,
1674
+ paddingBottom: 8
1675
+ },
1676
+ status: {
1677
+ fontSize: 13,
1678
+ color: apple.secondary,
1679
+ textAlign: "center",
1680
+ paddingHorizontal: 16,
1681
+ fontVariant: ["tabular-nums"]
1682
+ },
1683
+ warning: {
1684
+ fontSize: 13,
1685
+ color: palette.error,
1686
+ textAlign: "center",
1687
+ paddingHorizontal: 16,
1688
+ marginTop: 4
1689
+ }
1690
+ });
1691
+
1692
+ // src/react/ExplorerView.tsx
1693
+ import { LegendList } from "@legendapp/list/react-native";
1694
+ import { useCallback as useCallback3, useState as useState4 } from "react";
1695
+ import { Pressable as Pressable3, StyleSheet as StyleSheet5, Text as Text4, View as View6 } from "react-native";
1696
+
1697
+ // src/react/ExplorerFilters.tsx
1698
+ import { useState as useState3 } from "react";
1699
+ import { ScrollView, StyleSheet as StyleSheet4, Text as Text3, TextInput as TextInput2, View as View5 } from "react-native";
1700
+
1701
+ // src/react/InspectorSheet.tsx
1702
+ import {
1703
+ KeyboardAvoidingView,
1704
+ Modal as Modal2,
1705
+ Platform as Platform2,
1706
+ Pressable as Pressable2,
1707
+ StyleSheet as StyleSheet3,
1708
+ View as View4
1709
+ } from "react-native";
1710
+ import { SafeAreaProvider, SafeAreaView as SafeAreaView2 } from "react-native-safe-area-context";
1711
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1712
+ function InspectorSheet({
1713
+ title,
1714
+ close,
1715
+ closeLabel,
1716
+ primary,
1717
+ children
1718
+ }) {
1719
+ return /* @__PURE__ */ jsx4(Modal2, { visible: true, transparent: true, animationType: "slide", onRequestClose: close, children: /* @__PURE__ */ jsx4(SafeAreaProvider, { children: /* @__PURE__ */ jsxs3(
1720
+ KeyboardAvoidingView,
1721
+ {
1722
+ style: styles2.scrim,
1723
+ behavior: Platform2.OS === "ios" ? "padding" : void 0,
1724
+ children: [
1725
+ /* @__PURE__ */ jsx4(
1726
+ Pressable2,
1727
+ {
1728
+ accessibilityRole: "button",
1729
+ accessibilityLabel: closeLabel,
1730
+ onPress: close,
1731
+ style: StyleSheet3.absoluteFill
1732
+ }
1733
+ ),
1734
+ /* @__PURE__ */ jsxs3(
1735
+ SafeAreaView2,
1736
+ {
1737
+ edges: ["bottom", "left", "right"],
1738
+ style: styles2.sheet,
1739
+ children: [
1740
+ /* @__PURE__ */ jsx4(View4, { style: styles2.grabber }),
1741
+ /* @__PURE__ */ jsx4(
1742
+ NavBar,
1743
+ {
1744
+ left: /* @__PURE__ */ jsx4(TextButton, { label: closeLabel, onPress: close }),
1745
+ title,
1746
+ right: primary && /* @__PURE__ */ jsx4(
1747
+ TextButton,
1748
+ {
1749
+ label: primary.label,
1750
+ onPress: primary.onPress,
1751
+ disabled: primary.disabled,
1752
+ bold: true
1753
+ }
1754
+ )
1755
+ }
1756
+ ),
1757
+ children
1758
+ ]
1759
+ }
1760
+ )
1761
+ ]
1762
+ }
1763
+ ) }) });
1764
+ }
1765
+ var styles2 = StyleSheet3.create({
1766
+ scrim: {
1767
+ flex: 1,
1768
+ justifyContent: "flex-end",
1769
+ backgroundColor: palette.scrim
1770
+ },
1771
+ sheet: {
1772
+ height: "88%",
1773
+ backgroundColor: apple.grouped,
1774
+ borderTopLeftRadius: 12,
1775
+ borderTopRightRadius: 12,
1776
+ overflow: "hidden"
1777
+ },
1778
+ grabber: {
1779
+ alignSelf: "center",
1780
+ width: 36,
1781
+ height: 5,
1782
+ borderRadius: 3,
1783
+ backgroundColor: "#c7c7cc",
1784
+ marginTop: 6
1785
+ }
1786
+ });
1787
+
1788
+ // src/react/ExplorerFilters.tsx
1789
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1790
+ var toggle = (values, value) => values.includes(value) ? values.filter((item) => item !== value) : [...values, value];
1791
+ function ExplorerFilters({
1792
+ v,
1793
+ t,
1794
+ close
1795
+ }) {
1796
+ const [draft, setDraft] = useState3(v.query);
1797
+ const update = (patch) => setDraft((previous) => ({ ...previous, ...patch }));
1798
+ const sources = [
1799
+ ...new Set([...v.page.spans, ...v.page.marks].map(sourceOf))
1800
+ ].sort();
1801
+ const outcomes = v.mode === "traces" ? ["error", "success"] : ["success", "error", "cancelled", "interrupted"];
1802
+ const invalid = [draft.from, draft.to, draft.minDuration].some(
1803
+ (value) => value !== "" && (!Number.isFinite(Number(value)) || Number(value) < 0)
1804
+ ) || Boolean(draft.from && draft.to && Number(draft.from) > Number(draft.to));
1805
+ const field = (key, numeric2 = true) => /* @__PURE__ */ jsxs4(View5, { style: styles3.field, children: [
1806
+ /* @__PURE__ */ jsx5(Text3, { style: styles3.fieldLabel, children: t(key) }),
1807
+ /* @__PURE__ */ jsx5(
1808
+ TextInput2,
1809
+ {
1810
+ accessibilityLabel: t(key),
1811
+ value: draft[key],
1812
+ onChangeText: (value) => update({ [key]: value }),
1813
+ placeholder: "\u2014",
1814
+ placeholderTextColor: apple.secondary,
1815
+ keyboardType: numeric2 ? "decimal-pad" : "default",
1816
+ autoCapitalize: "none",
1817
+ autoCorrect: false,
1818
+ style: styles3.fieldInput
1819
+ }
1820
+ )
1821
+ ] });
1822
+ return /* @__PURE__ */ jsx5(
1823
+ InspectorSheet,
1824
+ {
1825
+ title: t("filters"),
1826
+ close,
1827
+ closeLabel: t("cancel"),
1828
+ primary: {
1829
+ label: t("applyFilters"),
1830
+ disabled: invalid,
1831
+ onPress: () => {
1832
+ v.updateQuery(draft);
1833
+ close();
1834
+ }
1835
+ },
1836
+ children: /* @__PURE__ */ jsxs4(
1837
+ ScrollView,
1838
+ {
1839
+ keyboardShouldPersistTaps: "handled",
1840
+ contentContainerStyle: { paddingBottom: 32 },
1841
+ children: [
1842
+ v.mode !== "marks" && /* @__PURE__ */ jsx5(Section, { header: t("outcome"), children: outcomes.map((outcome, index) => /* @__PURE__ */ jsx5(
1843
+ Row,
1844
+ {
1845
+ title: t(
1846
+ outcome === "success" && v.mode === "traces" ? "noErrors" : outcome
1847
+ ),
1848
+ checked: draft.outcomes.includes(outcome),
1849
+ onPress: () => update({ outcomes: toggle(draft.outcomes, outcome) }),
1850
+ last: index === outcomes.length - 1
1851
+ },
1852
+ outcome
1853
+ )) }),
1854
+ sources.length > 0 && /* @__PURE__ */ jsx5(Section, { header: t("source"), children: sources.map((source, index) => /* @__PURE__ */ jsx5(
1855
+ Row,
1856
+ {
1857
+ title: source,
1858
+ checked: draft.sources.includes(source),
1859
+ onPress: () => update({ sources: toggle(draft.sources, source) }),
1860
+ last: index === sources.length - 1
1861
+ },
1862
+ source
1863
+ )) }),
1864
+ /* @__PURE__ */ jsxs4(
1865
+ Section,
1866
+ {
1867
+ header: t("range"),
1868
+ footer: invalid ? t("invalidRange") : void 0,
1869
+ children: [
1870
+ v.mode !== "marks" && field("minDuration"),
1871
+ field("from"),
1872
+ field("to"),
1873
+ field("correlation", false)
1874
+ ]
1875
+ }
1876
+ ),
1877
+ /* @__PURE__ */ jsx5(Section, { children: /* @__PURE__ */ jsx5(
1878
+ Row,
1879
+ {
1880
+ title: t("reset"),
1881
+ tint: palette.error,
1882
+ onPress: () => setDraft(defaultQuery()),
1883
+ last: true
1884
+ }
1885
+ ) })
1886
+ ]
1887
+ }
1888
+ )
1889
+ }
1890
+ );
1891
+ }
1892
+ var styles3 = StyleSheet4.create({
1893
+ field: {
1894
+ flexDirection: "row",
1895
+ alignItems: "center",
1896
+ minHeight: 44,
1897
+ paddingHorizontal: 16,
1898
+ backgroundColor: apple.card,
1899
+ borderBottomWidth: StyleSheet4.hairlineWidth,
1900
+ borderColor: apple.separator,
1901
+ gap: 12
1902
+ },
1903
+ fieldLabel: { flex: 1, fontSize: 17, color: palette.text },
1904
+ fieldInput: {
1905
+ minWidth: 110,
1906
+ fontSize: 17,
1907
+ color: palette.text,
1908
+ textAlign: "right",
1909
+ paddingVertical: 8
1910
+ }
1911
+ });
1912
+
1913
+ // src/react/ExplorerView.tsx
1914
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1915
+ var toggle2 = (values, value) => values.includes(value) ? values.filter((item) => item !== value) : [...values, value];
1916
+ var isTrace = (item) => "spans" in item;
1917
+ var keyOf = (item) => isTrace(item) ? item.id : (
1918
+ // Sequence is unique per event; span ids can repeat if a collector reuses one.
1919
+ `${"spanId" in item ? "span" : "mark"}:${item.sequence}`
1920
+ );
1921
+ var sortsFor = (mode) => mode === "traces" ? ["newest", "oldest", "longest", "errors", "count", "name"] : mode === "spans" ? ["newest", "oldest", "longest", "shortest", "name"] : ["newest", "oldest", "name"];
1922
+ function ExplorerView({ v, t }) {
1923
+ const [filters, setFilters] = useState4(false);
1924
+ const menu = useMenu();
1925
+ const chips = [
1926
+ ...v.query.outcomes.map((value) => ({
1927
+ key: `outcome:${value}`,
1928
+ label: t(
1929
+ value === "success" && v.mode === "traces" ? "noErrors" : value
1930
+ ),
1931
+ clear: () => v.updateQuery({ outcomes: toggle2(v.query.outcomes, value) })
1932
+ })),
1933
+ ...v.query.sources.map((value) => ({
1934
+ key: `source:${value}`,
1935
+ label: value,
1936
+ clear: () => v.updateQuery({ sources: toggle2(v.query.sources, value) })
1937
+ })),
1938
+ ...["minDuration", "from", "to", "correlation"].filter((key) => v.query[key]).map((key) => ({
1939
+ key,
1940
+ label: `${t(key)}: ${v.query[key]}`,
1941
+ clear: () => v.updateQuery({ [key]: "" })
1942
+ }))
1943
+ ];
1944
+ const { open } = v;
1945
+ const renderItem = useCallback3(
1946
+ ({ item }) => {
1947
+ if (isTrace(item))
1948
+ return /* @__PURE__ */ jsx6(
1949
+ Row,
1950
+ {
1951
+ title: item.name,
1952
+ subtitle: t("traceRow", {
1953
+ spans: item.spans.length,
1954
+ errors: item.errors,
1955
+ source: item.sources.join(", ")
1956
+ }),
1957
+ value: `${item.duration.toFixed(0)} ms`,
1958
+ tint: item.errors ? palette.error : void 0,
1959
+ onPress: () => open({ kind: "trace", value: item })
1960
+ }
1961
+ );
1962
+ const span = "spanId" in item ? item : void 0;
1963
+ return /* @__PURE__ */ jsx6(
1964
+ Row,
1965
+ {
1966
+ title: item.name,
1967
+ subtitle: `${span ? t(span.outcome) : t("marks")} \xB7 ${sourceOf(item)} \xB7 ${(item.timestampMs / 1e3).toFixed(2)} s`,
1968
+ value: span ? `${span.durationMs.toFixed(0)} ms` : void 0,
1969
+ tint: span?.outcome === "error" ? palette.error : void 0,
1970
+ onPress: () => open(
1971
+ span ? { kind: "span", value: span } : { kind: "mark", value: item }
1972
+ )
1973
+ }
1974
+ );
1975
+ },
1976
+ [open, t]
1977
+ );
1978
+ return /* @__PURE__ */ jsxs5(View6, { style: styles4.root, children: [
1979
+ /* @__PURE__ */ jsx6(
1980
+ SearchField,
1981
+ {
1982
+ value: v.query.search,
1983
+ onChange: (search) => v.updateQuery({ search }),
1984
+ placeholder: t("exploreSearch")
1985
+ }
1986
+ ),
1987
+ /* @__PURE__ */ jsx6(
1988
+ Segmented,
1989
+ {
1990
+ options: ["traces", "spans", "marks"].map((mode) => ({
1991
+ value: mode,
1992
+ label: t(mode)
1993
+ })),
1994
+ value: v.mode,
1995
+ onChange: v.setMode
1996
+ }
1997
+ ),
1998
+ /* @__PURE__ */ jsxs5(View6, { style: styles4.toolbar, children: [
1999
+ /* @__PURE__ */ jsx6(
2000
+ Pressable3,
2001
+ {
2002
+ accessibilityRole: "button",
2003
+ onPress: () => menu.open({
2004
+ title: t("sort"),
2005
+ cancelLabel: t("cancel"),
2006
+ options: sortsFor(v.mode).map((sort) => ({
2007
+ label: t(sort),
2008
+ selected: sort === v.query.sort,
2009
+ onPress: () => v.updateQuery({ sort })
2010
+ }))
2011
+ }),
2012
+ hitSlop: 8,
2013
+ children: /* @__PURE__ */ jsxs5(Text4, { style: styles4.toolbarButton, children: [
2014
+ t("sortBy", { sort: t(v.query.sort) }),
2015
+ " \u2304"
2016
+ ] })
2017
+ }
2018
+ ),
2019
+ /* @__PURE__ */ jsx6(Text4, { style: styles4.count, children: t("resultCount", { matching: v.results.length, loaded: v.loaded }) }),
2020
+ /* @__PURE__ */ jsx6(
2021
+ Pressable3,
2022
+ {
2023
+ accessibilityRole: "button",
2024
+ onPress: () => {
2025
+ v.setHolding(true);
2026
+ setFilters(true);
2027
+ },
2028
+ hitSlop: 8,
2029
+ children: /* @__PURE__ */ jsxs5(Text4, { style: styles4.toolbarButton, children: [
2030
+ t("filter"),
2031
+ chips.length ? ` (${chips.length})` : "",
2032
+ " \u2304"
2033
+ ] })
2034
+ }
2035
+ )
2036
+ ] }),
2037
+ chips.length > 0 && /* @__PURE__ */ jsxs5(View6, { style: styles4.chips, children: [
2038
+ chips.map((chip) => /* @__PURE__ */ jsx6(
2039
+ Pressable3,
2040
+ {
2041
+ accessibilityRole: "button",
2042
+ accessibilityLabel: `${chip.label}, ${t("reset")}`,
2043
+ onPress: chip.clear,
2044
+ style: styles4.chip,
2045
+ children: /* @__PURE__ */ jsxs5(Text4, { style: styles4.chipText, children: [
2046
+ chip.label,
2047
+ " \u2715"
2048
+ ] })
2049
+ },
2050
+ chip.key
2051
+ )),
2052
+ /* @__PURE__ */ jsx6(
2053
+ Pressable3,
2054
+ {
2055
+ accessibilityRole: "button",
2056
+ onPress: v.resetQuery,
2057
+ style: styles4.chipClear,
2058
+ children: /* @__PURE__ */ jsx6(Text4, { style: styles4.toolbarButton, children: t("reset") })
2059
+ }
2060
+ )
2061
+ ] }),
2062
+ v.mode === "traces" && v.uncorrelated > 0 && !chips.length && /* @__PURE__ */ jsx6(Text4, { style: styles4.hint, children: t("uncorrelatedHint", { count: v.uncorrelated }) }),
2063
+ /* @__PURE__ */ jsx6(
2064
+ LegendList,
2065
+ {
2066
+ recycleItems: true,
2067
+ contentInsetAdjustmentBehavior: "automatic",
2068
+ style: styles4.list,
2069
+ data: v.results,
2070
+ keyExtractor: keyOf,
2071
+ estimatedItemSize: 64,
2072
+ keyboardShouldPersistTaps: "handled",
2073
+ keyboardDismissMode: "on-drag",
2074
+ initialScrollOffset: v.offsets.current[v.mode] ?? 0,
2075
+ onScroll: (event) => {
2076
+ v.offsets.current[v.mode] = event.nativeEvent.contentOffset.y;
2077
+ },
2078
+ scrollEventThrottle: 100,
2079
+ onScrollBeginDrag: () => v.setHolding(true),
2080
+ ListEmptyComponent: /* @__PURE__ */ jsx6(Text4, { style: styles4.empty, children: t("noResults") }),
2081
+ renderItem
2082
+ },
2083
+ v.mode
2084
+ ),
2085
+ menu.element,
2086
+ filters && /* @__PURE__ */ jsx6(ExplorerFilters, { v, t, close: () => setFilters(false) })
2087
+ ] });
2088
+ }
2089
+ var styles4 = StyleSheet5.create({
2090
+ root: { flex: 1 },
2091
+ toolbar: {
2092
+ flexDirection: "row",
2093
+ alignItems: "center",
2094
+ justifyContent: "space-between",
2095
+ paddingHorizontal: 16,
2096
+ paddingTop: 12,
2097
+ paddingBottom: 8,
2098
+ gap: 8
2099
+ },
2100
+ toolbarButton: { fontSize: 15, color: palette.accent, fontWeight: "500" },
2101
+ count: { flex: 1, textAlign: "center", fontSize: 13, color: apple.secondary },
2102
+ chips: {
2103
+ flexDirection: "row",
2104
+ flexWrap: "wrap",
2105
+ gap: 6,
2106
+ paddingHorizontal: 16,
2107
+ paddingBottom: 8
2108
+ },
2109
+ chip: {
2110
+ paddingHorizontal: 10,
2111
+ paddingVertical: 4,
2112
+ borderRadius: 14,
2113
+ backgroundColor: palette.selected
2114
+ },
2115
+ chipText: { fontSize: 13, color: palette.accent },
2116
+ chipClear: { paddingHorizontal: 4, paddingVertical: 4 },
2117
+ hint: {
2118
+ fontSize: 13,
2119
+ color: apple.secondary,
2120
+ paddingHorizontal: 16,
2121
+ paddingBottom: 8
2122
+ },
2123
+ list: { flex: 1, backgroundColor: apple.card },
2124
+ empty: {
2125
+ fontSize: 15,
2126
+ color: apple.secondary,
2127
+ textAlign: "center",
2128
+ padding: 32
2129
+ }
2130
+ });
2131
+
2132
+ // src/react/DetailView.tsx
2133
+ import { LegendList as LegendList2 } from "@legendapp/list/react-native";
2134
+ import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState5 } from "react";
2135
+ import { Pressable as Pressable4, ScrollView as ScrollView2, StyleSheet as StyleSheet6, Text as Text5, View as View7 } from "react-native";
2136
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2137
+ var fixed = (value) => value.toFixed(1);
2138
+ function MetricDetail({
2139
+ series,
2140
+ t
2141
+ }) {
2142
+ const stats = [
2143
+ ["latest", series.latest],
2144
+ ["median", series.median],
2145
+ ["p95", series.p95],
2146
+ ["min", series.min],
2147
+ ["max", series.peak]
2148
+ ];
2149
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
2150
+ /* @__PURE__ */ jsxs6(Section, { header: series.name, footer: t("metricsScope"), children: [
2151
+ /* @__PURE__ */ jsxs6(View7, { style: styles5.chartCard, children: [
2152
+ /* @__PURE__ */ jsx7(View7, { style: styles5.chart, children: series.samples.map((sample) => /* @__PURE__ */ jsx7(
2153
+ View7,
2154
+ {
2155
+ accessibilityLabel: `${sample.value.toFixed(2)} ${sample.unit}`,
2156
+ style: [
2157
+ styles5.bar,
2158
+ {
2159
+ height: `${Math.max(2, Math.abs(sample.value) / series.max * 100)}%`
2160
+ }
2161
+ ]
2162
+ },
2163
+ sample.sequence
2164
+ )) }),
2165
+ /* @__PURE__ */ jsx7(Text5, { style: styles5.caption, children: t("metricWindow", {
2166
+ displayed: series.samples.length,
2167
+ retained: series.retainedCount,
2168
+ start: fixed(series.startMs),
2169
+ end: fixed(series.endMs)
2170
+ }) })
2171
+ ] }),
2172
+ stats.map(([key, value]) => /* @__PURE__ */ jsx7(
2173
+ Row,
2174
+ {
2175
+ title: t(`stat_${key}`),
2176
+ value: formatMetric(value, series.unit)
2177
+ },
2178
+ key
2179
+ )),
2180
+ /* @__PURE__ */ jsx7(Row, { title: t("samples"), value: String(series.retainedCount), last: true })
2181
+ ] }),
2182
+ !!series.samples.at(-1)?.attributes.length && /* @__PURE__ */ jsx7(Section, { header: t("attributes"), children: series.samples.at(-1).attributes.map((attribute2, index, all) => /* @__PURE__ */ jsx7(
2183
+ Row,
2184
+ {
2185
+ title: attribute2.key,
2186
+ value: attribute2.value,
2187
+ last: index === all.length - 1
2188
+ },
2189
+ attribute2.key
2190
+ )) })
2191
+ ] });
2192
+ }
2193
+ function DetailView({ v, t }) {
2194
+ const detail = v.detail;
2195
+ const key = detail.kind + ":" + (detail.kind === "trace" ? detail.value.id : detail.kind === "span" ? detail.value.spanId : detail.kind === "mark" ? detail.value.sequence : detail.value.name);
2196
+ const saved = v.detailState.current[key];
2197
+ const [collapsed, setCollapsed] = useState5(
2198
+ new Set(saved?.collapsed ?? [])
2199
+ );
2200
+ const [attributes, setAttributes] = useState5(saved?.attributes ?? "");
2201
+ const [layout, setLayout] = useState5(
2202
+ saved?.layout ?? "waterfall"
2203
+ );
2204
+ const offset = useRef2(saved?.offset ?? 0);
2205
+ useEffect2(() => {
2206
+ if (Object.keys(v.detailState.current).length > 128)
2207
+ delete v.detailState.current[Object.keys(v.detailState.current)[0]];
2208
+ v.detailState.current[key] = {
2209
+ collapsed: [...collapsed],
2210
+ attributes,
2211
+ layout,
2212
+ offset: offset.current
2213
+ };
2214
+ }, [collapsed, attributes, layout, key, v.detailState]);
2215
+ const scrollProps = {
2216
+ contentOffset: { x: 0, y: offset.current },
2217
+ scrollEventThrottle: 100,
2218
+ onScroll: (event2) => {
2219
+ offset.current = event2.nativeEvent.contentOffset.y;
2220
+ if (v.detailState.current[key])
2221
+ v.detailState.current[key].offset = offset.current;
2222
+ }
2223
+ };
2224
+ const items = useMemo2(
2225
+ () => detail.kind === "trace" ? [
2226
+ ...hierarchyRows(detail.value.spans, collapsed).map((row) => ({
2227
+ kind: "span",
2228
+ row
2229
+ })),
2230
+ ...detail.value.marks.map((mark) => ({
2231
+ kind: "mark",
2232
+ mark
2233
+ }))
2234
+ ] : [],
2235
+ [detail, collapsed]
2236
+ );
2237
+ const traceIds = useMemo2(
2238
+ () => new Set(
2239
+ detail.kind === "trace" ? detail.value.spans.map((s2) => s2.spanId) : []
2240
+ ),
2241
+ [detail]
2242
+ );
2243
+ if (detail.kind === "trace") {
2244
+ const trace2 = detail.value;
2245
+ const toggle3 = (spanId) => setCollapsed((previous) => {
2246
+ const next = new Set(previous);
2247
+ if (next.has(spanId)) next.delete(spanId);
2248
+ else next.add(spanId);
2249
+ return next;
2250
+ });
2251
+ return /* @__PURE__ */ jsx7(
2252
+ LegendList2,
2253
+ {
2254
+ recycleItems: true,
2255
+ ...scrollProps,
2256
+ style: styles5.list,
2257
+ initialScrollOffset: offset.current,
2258
+ data: items,
2259
+ estimatedItemSize: 64,
2260
+ keyExtractor: (item) => item.kind === "span" ? `span:${item.row.span.sequence}` : `mark:${item.mark.sequence}`,
2261
+ ListHeaderComponent: /* @__PURE__ */ jsxs6(View7, { style: styles5.traceHeader, children: [
2262
+ /* @__PURE__ */ jsx7(Text5, { style: styles5.title, children: trace2.name }),
2263
+ /* @__PURE__ */ jsx7(Text5, { style: styles5.subtitle, children: t("traceSummary", {
2264
+ duration: fixed(trace2.duration),
2265
+ events: trace2.spans.length + trace2.marks.length,
2266
+ errors: trace2.errors
2267
+ }) }),
2268
+ /* @__PURE__ */ jsxs6(Text5, { selectable: true, style: styles5.caption, children: [
2269
+ trace2.id,
2270
+ " \xB7 ",
2271
+ fixed(trace2.start),
2272
+ "\u2013",
2273
+ fixed(trace2.start + trace2.duration),
2274
+ " ms"
2275
+ ] }),
2276
+ /* @__PURE__ */ jsx7(EvictionNotice, { v, t }),
2277
+ /* @__PURE__ */ jsx7(FrozenNotice, { v, t }),
2278
+ /* @__PURE__ */ jsx7(
2279
+ Segmented,
2280
+ {
2281
+ options: [
2282
+ { value: "waterfall", label: t("waterfall") },
2283
+ { value: "list", label: t("spans") }
2284
+ ],
2285
+ value: layout,
2286
+ onChange: setLayout
2287
+ }
2288
+ ),
2289
+ /* @__PURE__ */ jsxs6(View7, { style: styles5.headerActions, children: [
2290
+ /* @__PURE__ */ jsx7(
2291
+ TextButton,
2292
+ {
2293
+ label: t("expandAll"),
2294
+ onPress: () => setCollapsed(/* @__PURE__ */ new Set())
2295
+ }
2296
+ ),
2297
+ /* @__PURE__ */ jsx7(
2298
+ TextButton,
2299
+ {
2300
+ label: t("collapseAll"),
2301
+ onPress: () => setCollapsed(new Set(trace2.spans.map((span2) => span2.spanId)))
2302
+ }
2303
+ )
2304
+ ] })
2305
+ ] }),
2306
+ ListEmptyComponent: /* @__PURE__ */ jsx7(Text5, { style: styles5.empty, children: t("noSpans") }),
2307
+ renderItem: ({ item }) => {
2308
+ if (item.kind === "mark")
2309
+ return /* @__PURE__ */ jsx7(
2310
+ Row,
2311
+ {
2312
+ icon: "\u25C6",
2313
+ iconColor: apple.secondary,
2314
+ title: item.mark.name,
2315
+ value: `${fixed(item.mark.timestampMs)} ms`,
2316
+ onPress: () => v.open({ kind: "mark", value: item.mark })
2317
+ }
2318
+ );
2319
+ const { row } = item;
2320
+ const error = row.span.outcome === "error";
2321
+ return /* @__PURE__ */ jsxs6(
2322
+ Pressable4,
2323
+ {
2324
+ accessibilityRole: "button",
2325
+ onPress: () => v.open({ kind: "span", value: row.span }),
2326
+ style: ({ pressed }) => [
2327
+ styles5.spanRow,
2328
+ pressed && styles5.pressed
2329
+ ],
2330
+ children: [
2331
+ /* @__PURE__ */ jsxs6(
2332
+ View7,
2333
+ {
2334
+ style: [
2335
+ styles5.spanLine,
2336
+ { paddingLeft: Math.min(8, row.depth) * 12 }
2337
+ ],
2338
+ children: [
2339
+ row.children > 0 ? /* @__PURE__ */ jsx7(
2340
+ Pressable4,
2341
+ {
2342
+ accessibilityRole: "button",
2343
+ accessibilityLabel: `${collapsed.has(row.span.spanId) ? t("expandAll") : t("collapseAll")} ${row.span.name}`,
2344
+ onPress: () => toggle3(row.span.spanId),
2345
+ hitSlop: 10,
2346
+ children: /* @__PURE__ */ jsx7(Text5, { style: styles5.disclosure, children: collapsed.has(row.span.spanId) ? "\u25B8" : "\u25BE" })
2347
+ }
2348
+ ) : /* @__PURE__ */ jsx7(Text5, { style: styles5.disclosure, children: " " }),
2349
+ /* @__PURE__ */ jsxs6(
2350
+ Text5,
2351
+ {
2352
+ style: [styles5.spanName, error && styles5.error],
2353
+ numberOfLines: 1,
2354
+ children: [
2355
+ row.span.name,
2356
+ row.children > 0 && /* @__PURE__ */ jsxs6(Text5, { style: styles5.subtitle, children: [
2357
+ " (",
2358
+ row.children,
2359
+ ")"
2360
+ ] })
2361
+ ]
2362
+ }
2363
+ ),
2364
+ /* @__PURE__ */ jsxs6(Text5, { style: [styles5.spanValue, error && styles5.error], children: [
2365
+ row.span.durationMs.toFixed(
2366
+ row.span.durationMs < 10 ? 1 : 0
2367
+ ),
2368
+ " ",
2369
+ "ms"
2370
+ ] })
2371
+ ]
2372
+ }
2373
+ ),
2374
+ v.query.search && matchesSearch(row.span, v.query.search.toLowerCase()) || row.span.parentSpanId && !traceIds.has(row.span.parentSpanId) ? /* @__PURE__ */ jsx7(
2375
+ Text5,
2376
+ {
2377
+ style: [
2378
+ styles5.caption,
2379
+ { paddingLeft: Math.min(8, row.depth) * 12 + 20 }
2380
+ ],
2381
+ children: v.query.search && matchesSearch(row.span, v.query.search.toLowerCase()) ? t("searchMatch") : t("missingParent")
2382
+ }
2383
+ ) : null,
2384
+ layout === "waterfall" && /* @__PURE__ */ jsx7(View7, { style: styles5.track, children: /* @__PURE__ */ jsx7(
2385
+ View7,
2386
+ {
2387
+ style: [
2388
+ styles5.segment,
2389
+ {
2390
+ marginLeft: `${row.offset * 100}%`,
2391
+ width: `${Math.max(0.5, row.width * 100)}%`,
2392
+ backgroundColor: error ? palette.error : palette.accent
2393
+ }
2394
+ ]
2395
+ }
2396
+ ) })
2397
+ ]
2398
+ }
2399
+ );
2400
+ }
2401
+ }
2402
+ );
2403
+ }
2404
+ if (detail.kind === "metric")
2405
+ return /* @__PURE__ */ jsxs6(ScrollView2, { ...scrollProps, contentContainerStyle: styles5.content, children: [
2406
+ /* @__PURE__ */ jsx7(EvictionNotice, { v, t }),
2407
+ /* @__PURE__ */ jsx7(MetricDetail, { series: detail.value, t })
2408
+ ] });
2409
+ const event = detail.value;
2410
+ const span = detail.kind === "span" ? detail.value : void 0;
2411
+ const parent = span ? v.page.spans.find((s2) => s2.spanId === span.parentSpanId) : void 0;
2412
+ const children = span ? v.page.spans.filter((s2) => s2.parentSpanId === span.spanId) : [];
2413
+ const trace = span ? v.traceFor(span) : void 0;
2414
+ const shownAttributes = event.attributes.filter(
2415
+ (a) => `${a.key} ${a.value}`.toLowerCase().includes(attributes.toLowerCase())
2416
+ );
2417
+ return /* @__PURE__ */ jsxs6(
2418
+ ScrollView2,
2419
+ {
2420
+ ...scrollProps,
2421
+ contentContainerStyle: styles5.content,
2422
+ keyboardShouldPersistTaps: "handled",
2423
+ children: [
2424
+ /* @__PURE__ */ jsxs6(View7, { style: styles5.traceHeader, children: [
2425
+ /* @__PURE__ */ jsx7(Text5, { style: styles5.title, selectable: true, children: event.name }),
2426
+ /* @__PURE__ */ jsx7(EvictionNotice, { v, t })
2427
+ ] }),
2428
+ /* @__PURE__ */ jsxs6(Section, { children: [
2429
+ span && /* @__PURE__ */ jsx7(
2430
+ Row,
2431
+ {
2432
+ title: t("outcome"),
2433
+ value: t(span.outcome),
2434
+ tint: span.outcome === "error" ? palette.error : void 0
2435
+ }
2436
+ ),
2437
+ span && /* @__PURE__ */ jsx7(
2438
+ Row,
2439
+ {
2440
+ title: t("duration"),
2441
+ value: `${span.durationMs.toFixed(2)} ms`
2442
+ }
2443
+ ),
2444
+ /* @__PURE__ */ jsx7(Row, { title: t("source"), value: sourceOf(event) }),
2445
+ /* @__PURE__ */ jsx7(
2446
+ Row,
2447
+ {
2448
+ title: t("time"),
2449
+ value: `${fixed(event.timestampMs)}\u2013${fixed(event.timestampMs + (span?.durationMs ?? 0))} ms`
2450
+ }
2451
+ ),
2452
+ /* @__PURE__ */ jsx7(
2453
+ Row,
2454
+ {
2455
+ title: t("correlationId"),
2456
+ subtitle: event.correlationId || t("uncorrelated"),
2457
+ last: !span
2458
+ }
2459
+ ),
2460
+ span && /* @__PURE__ */ jsx7(Row, { title: t("spanId"), subtitle: span.spanId, last: true })
2461
+ ] }),
2462
+ /* @__PURE__ */ jsxs6(
2463
+ Section,
2464
+ {
2465
+ footer: span?.parentSpanId && !parent ? t("missingParent") : void 0,
2466
+ children: [
2467
+ trace && /* @__PURE__ */ jsx7(
2468
+ Row,
2469
+ {
2470
+ icon: "\u2630",
2471
+ title: t("wholeTrace"),
2472
+ onPress: () => v.open({ kind: "trace", value: trace })
2473
+ }
2474
+ ),
2475
+ parent && /* @__PURE__ */ jsx7(
2476
+ Row,
2477
+ {
2478
+ icon: "\u2191",
2479
+ title: t("parent"),
2480
+ subtitle: parent.name,
2481
+ onPress: () => v.open({ kind: "span", value: parent })
2482
+ }
2483
+ ),
2484
+ /* @__PURE__ */ jsx7(
2485
+ Row,
2486
+ {
2487
+ icon: "\u2315",
2488
+ title: t("similar"),
2489
+ onPress: () => (detail.kind === "mark" ? v.showMarks : v.showSpans)({
2490
+ search: event.name
2491
+ }),
2492
+ last: true
2493
+ }
2494
+ )
2495
+ ]
2496
+ }
2497
+ ),
2498
+ event.attributes.length > 0 && /* @__PURE__ */ jsxs6(Fragment3, { children: [
2499
+ /* @__PURE__ */ jsx7(Text5, { style: styles5.sectionTitle, children: t("attributes") }),
2500
+ event.attributes.length > 6 && /* @__PURE__ */ jsx7(
2501
+ SearchField,
2502
+ {
2503
+ value: attributes,
2504
+ onChange: setAttributes,
2505
+ placeholder: t("attributeSearch")
2506
+ }
2507
+ ),
2508
+ /* @__PURE__ */ jsx7(Section, { children: shownAttributes.map((a, index) => /* @__PURE__ */ jsx7(
2509
+ Row,
2510
+ {
2511
+ title: a.key,
2512
+ subtitle: a.value,
2513
+ last: index === shownAttributes.length - 1
2514
+ },
2515
+ a.key
2516
+ )) })
2517
+ ] }),
2518
+ children.length > 0 && /* @__PURE__ */ jsx7(Section, { header: t("children"), children: children.map((child, index) => /* @__PURE__ */ jsx7(
2519
+ Row,
2520
+ {
2521
+ title: child.name,
2522
+ value: `${child.durationMs.toFixed(1)} ms`,
2523
+ tint: child.outcome === "error" ? palette.error : void 0,
2524
+ onPress: () => v.open({ kind: "span", value: child }),
2525
+ last: index === children.length - 1
2526
+ },
2527
+ child.spanId
2528
+ )) })
2529
+ ]
2530
+ }
2531
+ );
2532
+ }
2533
+ function EvictionNotice({ v, t }) {
2534
+ const earliest = useStore(v.telemetry, (s2) => s2.pending?.earliestSequence) ?? v.page.earliestSequence;
2535
+ const detail = v.detail;
2536
+ if (!detail) return null;
2537
+ const events = detail.kind === "trace" ? [...detail.value.spans, ...detail.value.marks] : detail.kind === "metric" ? detail.value.samples : [detail.value];
2538
+ return events.some((event) => event.sequence < earliest) ? /* @__PURE__ */ jsx7(Text5, { style: [styles5.caption, styles5.error], children: t("evicted") }) : null;
2539
+ }
2540
+ function FrozenNotice({ v, t }) {
2541
+ const pending = useStore(v.telemetry, (s2) => s2.pending);
2542
+ return pending ? /* @__PURE__ */ jsx7(Text5, { style: styles5.caption, children: t("detailFrozen") }) : null;
2543
+ }
2544
+ var styles5 = StyleSheet6.create({
2545
+ list: { flex: 1 },
2546
+ content: { paddingBottom: 32 },
2547
+ traceHeader: { paddingHorizontal: 16, paddingTop: 16, gap: 4 },
2548
+ title: { fontSize: 22, fontWeight: "700", color: palette.text },
2549
+ subtitle: { fontSize: 15, color: apple.secondary },
2550
+ caption: { fontSize: 12, color: apple.secondary },
2551
+ error: { color: palette.error },
2552
+ headerActions: {
2553
+ flexDirection: "row",
2554
+ justifyContent: "space-between",
2555
+ marginTop: 4,
2556
+ marginBottom: 8
2557
+ },
2558
+ sectionTitle: {
2559
+ fontSize: 13,
2560
+ color: apple.secondary,
2561
+ textTransform: "uppercase",
2562
+ marginLeft: 32,
2563
+ marginTop: 20
2564
+ },
2565
+ spanRow: {
2566
+ backgroundColor: apple.card,
2567
+ paddingHorizontal: 16,
2568
+ paddingVertical: 8,
2569
+ gap: 6,
2570
+ borderBottomWidth: StyleSheet6.hairlineWidth,
2571
+ borderColor: apple.separator
2572
+ },
2573
+ pressed: { backgroundColor: "#e5e5ea" },
2574
+ spanLine: { flexDirection: "row", alignItems: "center", gap: 6 },
2575
+ disclosure: { width: 14, fontSize: 13, color: apple.secondary },
2576
+ spanName: { flex: 1, fontSize: 15, color: palette.text },
2577
+ spanValue: {
2578
+ fontSize: 13,
2579
+ color: apple.secondary,
2580
+ fontVariant: ["tabular-nums"]
2581
+ },
2582
+ track: {
2583
+ height: 4,
2584
+ borderRadius: 2,
2585
+ backgroundColor: apple.fill,
2586
+ overflow: "hidden"
2587
+ },
2588
+ segment: { height: 4, borderRadius: 2 },
2589
+ chartCard: { padding: 16, gap: 8, backgroundColor: apple.card },
2590
+ chart: { height: 96, flexDirection: "row", alignItems: "flex-end", gap: 2 },
2591
+ bar: {
2592
+ flex: 1,
2593
+ minHeight: 2,
2594
+ borderTopLeftRadius: 2,
2595
+ borderTopRightRadius: 2,
2596
+ backgroundColor: palette.accent
2597
+ },
2598
+ empty: {
2599
+ fontSize: 15,
2600
+ color: apple.secondary,
2601
+ textAlign: "center",
2602
+ padding: 32
2603
+ }
2604
+ });
2605
+
2606
+ // src/react/MetricsView.tsx
2607
+ import { LegendList as LegendList3 } from "@legendapp/list/react-native";
2608
+ import { useCallback as useCallback4, useMemo as useMemo3 } from "react";
2609
+ import { Pressable as Pressable5, StyleSheet as StyleSheet7, Text as Text6, View as View8 } from "react-native";
2610
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2611
+ var categories = ["all", "runtime", "operations", "custom"];
2612
+ var sorts = ["name", "count", "p95"];
2613
+ var category = (name) => name.startsWith("duration: ") ? "operations" : /^(js|ui|process|app)\./.test(name) ? "runtime" : "custom";
2614
+ function MetricsView({ v, t }) {
2615
+ const menu = useMenu();
2616
+ const metrics = useMemo3(
2617
+ () => v.metrics.filter(
2618
+ (series) => (v.metricCategory === "all" || category(series.name) === v.metricCategory) && series.name.toLowerCase().includes(v.metricQuery.toLowerCase())
2619
+ ).sort(
2620
+ (a, b) => v.metricSort === "name" ? a.name.localeCompare(b.name) : v.metricSort === "count" ? b.retainedCount - a.retainedCount || a.name.localeCompare(b.name) : b.p95 - a.p95 || a.name.localeCompare(b.name)
2621
+ ),
2622
+ [v.metrics, v.metricCategory, v.metricQuery, v.metricSort]
2623
+ );
2624
+ const { open } = v;
2625
+ const renderItem = useCallback4(
2626
+ ({ item }) => /* @__PURE__ */ jsx8(
2627
+ Row,
2628
+ {
2629
+ title: item.metric,
2630
+ subtitle: t("metricBrief", {
2631
+ latest: formatMetric(item.latest, item.unit),
2632
+ p95: formatMetric(item.p95, item.unit),
2633
+ count: item.retainedCount
2634
+ }),
2635
+ value: formatMetric(item.latest, item.unit),
2636
+ onPress: () => open({ kind: "metric", value: item })
2637
+ }
2638
+ ),
2639
+ [open, t]
2640
+ );
2641
+ return /* @__PURE__ */ jsxs7(View8, { style: styles6.root, children: [
2642
+ /* @__PURE__ */ jsx8(
2643
+ SearchField,
2644
+ {
2645
+ value: v.metricQuery,
2646
+ onChange: v.setMetricQuery,
2647
+ placeholder: t("metricSearch")
2648
+ }
2649
+ ),
2650
+ /* @__PURE__ */ jsx8(
2651
+ Segmented,
2652
+ {
2653
+ options: categories.map((value) => ({
2654
+ value,
2655
+ label: t(value === "operations" ? "operationsShort" : value)
2656
+ })),
2657
+ value: v.metricCategory,
2658
+ onChange: v.setMetricCategory
2659
+ }
2660
+ ),
2661
+ /* @__PURE__ */ jsxs7(View8, { style: styles6.toolbar, children: [
2662
+ /* @__PURE__ */ jsx8(
2663
+ Pressable5,
2664
+ {
2665
+ accessibilityRole: "button",
2666
+ onPress: () => menu.open({
2667
+ title: t("sort"),
2668
+ cancelLabel: t("cancel"),
2669
+ options: sorts.map((sort) => ({
2670
+ label: t(sort),
2671
+ selected: sort === v.metricSort,
2672
+ onPress: () => v.setMetricSort(sort)
2673
+ }))
2674
+ }),
2675
+ hitSlop: 8,
2676
+ children: /* @__PURE__ */ jsxs7(Text6, { style: styles6.toolbarButton, children: [
2677
+ t("sortBy", {
2678
+ sort: t(v.metricSort)
2679
+ }),
2680
+ " ",
2681
+ "\u2304"
2682
+ ] })
2683
+ }
2684
+ ),
2685
+ /* @__PURE__ */ jsx8(Text6, { style: styles6.count, children: metrics.length })
2686
+ ] }),
2687
+ /* @__PURE__ */ jsx8(
2688
+ LegendList3,
2689
+ {
2690
+ recycleItems: true,
2691
+ contentInsetAdjustmentBehavior: "automatic",
2692
+ style: styles6.list,
2693
+ data: metrics,
2694
+ keyExtractor: (series) => series.name,
2695
+ estimatedItemSize: 56,
2696
+ keyboardDismissMode: "on-drag",
2697
+ initialScrollOffset: v.offsets.current.metrics ?? 0,
2698
+ onScroll: (event) => {
2699
+ v.offsets.current.metrics = event.nativeEvent.contentOffset.y;
2700
+ },
2701
+ scrollEventThrottle: 100,
2702
+ onScrollBeginDrag: () => v.setHolding(true),
2703
+ ListEmptyComponent: /* @__PURE__ */ jsx8(Text6, { style: styles6.empty, children: t("emptyMetrics") }),
2704
+ renderItem,
2705
+ ListFooterComponent: v.metricCategory === "operations" && v.operations.length ? /* @__PURE__ */ jsxs7(View8, { children: [
2706
+ /* @__PURE__ */ jsx8(Text6, { style: styles6.header, children: t("operations") }),
2707
+ v.operations.filter(
2708
+ (op) => op.name.toLowerCase().includes(v.metricQuery.toLowerCase())
2709
+ ).slice(0, 40).map((op) => /* @__PURE__ */ jsx8(
2710
+ Row,
2711
+ {
2712
+ title: op.name,
2713
+ subtitle: t("operationStats", {
2714
+ ...op,
2715
+ errorRate: op.errorRate === void 0 ? "\u2014" : `${op.errorRate.toFixed(1)}%`
2716
+ }),
2717
+ tint: op.errors ? palette.error : void 0,
2718
+ onPress: () => v.showSpans({ search: op.name })
2719
+ },
2720
+ op.name
2721
+ ))
2722
+ ] }) : null
2723
+ }
2724
+ ),
2725
+ menu.element
2726
+ ] });
2727
+ }
2728
+ var styles6 = StyleSheet7.create({
2729
+ root: { flex: 1 },
2730
+ toolbar: {
2731
+ flexDirection: "row",
2732
+ alignItems: "center",
2733
+ justifyContent: "space-between",
2734
+ paddingHorizontal: 16,
2735
+ paddingTop: 10,
2736
+ paddingBottom: 6
2737
+ },
2738
+ toolbarButton: { fontSize: 15, color: palette.accent, fontWeight: "500" },
2739
+ count: { fontSize: 13, color: apple.secondary },
2740
+ list: { flex: 1, backgroundColor: apple.card },
2741
+ header: {
2742
+ fontSize: 13,
2743
+ fontWeight: "600",
2744
+ color: apple.secondary,
2745
+ textTransform: "uppercase",
2746
+ paddingHorizontal: 16,
2747
+ paddingTop: 18,
2748
+ paddingBottom: 6,
2749
+ backgroundColor: apple.grouped
2750
+ },
2751
+ empty: {
2752
+ fontSize: 15,
2753
+ color: apple.secondary,
2754
+ textAlign: "center",
2755
+ padding: 32
2756
+ }
2757
+ });
2758
+
2759
+ // src/react/SummaryView.tsx
2760
+ import { ScrollView as ScrollView3 } from "react-native";
2761
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2762
+ var capital = (id) => id[0].toUpperCase() + id.slice(1);
2763
+ var issueLabel = (issue) => `issue${capital(issue.kind)}`;
2764
+ var ms = (value) => value === void 0 ? "\u2014" : formatMetric(value, "ms");
2765
+ var MAX_ISSUES = 20;
2766
+ var openEvent = (v, event) => {
2767
+ if ("spanId" in event) v.open({ kind: "span", value: event });
2768
+ else if ("unit" in event) {
2769
+ const series = v.metrics.find(
2770
+ (s2) => s2.name === `${event.name} (${event.unit})`
2771
+ );
2772
+ if (series) v.open({ kind: "metric", value: series });
2773
+ } else v.open({ kind: "mark", value: event });
2774
+ };
2775
+ function SummaryView({
2776
+ v,
2777
+ t,
2778
+ budgetMs = defaultBudgets.stallMs
2779
+ }) {
2780
+ const explore = (id) => {
2781
+ if (id === "startup" || id === "responsiveness" || id === "resources") {
2782
+ v.setTab("metrics");
2783
+ v.setMetricCategory("runtime");
2784
+ } else if (id === "errors") v.showMarks({ sources: ["error"] });
2785
+ else if (id === "network")
2786
+ v.showSpans({ sources: ["network"], sort: "longest" });
2787
+ else if (id === "screens") v.showSpans({ sources: ["navigation"] });
2788
+ else v.showSpans({});
2789
+ };
2790
+ const shown = v.issues.slice(0, MAX_ISSUES);
2791
+ return /* @__PURE__ */ jsxs8(
2792
+ ScrollView3,
2793
+ {
2794
+ contentInsetAdjustmentBehavior: "automatic",
2795
+ contentContainerStyle: { paddingBottom: 32 },
2796
+ children: [
2797
+ /* @__PURE__ */ jsx9(
2798
+ Section,
2799
+ {
2800
+ header: v.issues.length ? t("issues", { count: v.issues.length }) : void 0,
2801
+ footer: v.issues.length > shown.length ? t("moreIssues", { count: v.issues.length - shown.length }) : void 0,
2802
+ children: shown.length ? shown.map((issue, index) => /* @__PURE__ */ jsx9(
2803
+ Row,
2804
+ {
2805
+ icon: "\u26A0",
2806
+ iconColor: palette.error,
2807
+ title: issue.title,
2808
+ subtitle: `${t(issueLabel(issue))} \xB7 +${(issue.event.timestampMs / 1e3).toFixed(1)} s`,
2809
+ value: issue.valueMs === void 0 ? void 0 : formatMetric(issue.valueMs, "ms"),
2810
+ tint: palette.error,
2811
+ onPress: () => openEvent(v, issue.event),
2812
+ last: index === shown.length - 1
2813
+ },
2814
+ index
2815
+ )) : /* @__PURE__ */ jsx9(Row, { icon: "\u2713", iconColor: "#34c759", title: t("noIssues"), last: true })
2816
+ }
2817
+ ),
2818
+ /* @__PURE__ */ jsx9(Section, { header: t("topics"), footer: t("summaryScope"), children: v.topics.map((topic, index) => {
2819
+ const name = capital(topic.id);
2820
+ const hasIssues = topic.issues.length > 0;
2821
+ return /* @__PURE__ */ jsx9(
2822
+ Row,
2823
+ {
2824
+ icon: topicGlyph[topic.id].icon,
2825
+ iconColor: topic.tracked ? topicGlyph[topic.id].color : apple.secondary,
2826
+ title: t(`topic${name}`),
2827
+ subtitle: !topic.tracked ? `${t("notTracked")} \xB7 ${t(`hint${name}`)}` : !topic.count && topic.id !== "responsiveness" ? t("quiet") : t(`headline${name}`, {
2828
+ count: topic.count,
2829
+ value: ms(topic.value),
2830
+ budget: budgetMs,
2831
+ ...topic.values
2832
+ }),
2833
+ value: hasIssues ? String(topic.issues.length) : void 0,
2834
+ tint: hasIssues ? palette.error : void 0,
2835
+ onPress: () => explore(topic.id),
2836
+ last: index === v.topics.length - 1
2837
+ },
2838
+ topic.id
2839
+ );
2840
+ }) })
2841
+ ]
2842
+ }
2843
+ );
2844
+ }
2845
+
2846
+ // src/react/TimelineView.tsx
2847
+ import { LegendList as LegendList4 } from "@legendapp/list/react-native";
2848
+ import { useCallback as useCallback5 } from "react";
2849
+ import { StyleSheet as StyleSheet8, Text as Text7 } from "react-native";
2850
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2851
+ var valueOf = (item) => "durationMs" in item.event ? formatMetric(item.event.durationMs, "ms") : "value" in item.event ? formatMetric(item.event.value, item.event.unit) : void 0;
2852
+ var messageOf = (item) => item.event.attributes.find((a) => a.key === "message")?.value;
2853
+ function TimelineView({ v, t }) {
2854
+ const renderItem = useCallback5(
2855
+ ({ item }) => {
2856
+ if (item.kind === "screen")
2857
+ return /* @__PURE__ */ jsxs9(Text7, { style: styles7.header, children: [
2858
+ item.name || t("beforeNavigation"),
2859
+ " \xB7",
2860
+ " ",
2861
+ (item.timestampMs / 1e3).toFixed(1),
2862
+ " s"
2863
+ ] });
2864
+ const message = messageOf(item);
2865
+ return /* @__PURE__ */ jsx10(
2866
+ Row,
2867
+ {
2868
+ icon: topicGlyph[item.topic].icon,
2869
+ iconColor: topicGlyph[item.topic].color,
2870
+ title: item.event.name,
2871
+ subtitle: `${(item.event.timestampMs / 1e3).toFixed(2)} s${message ? ` \xB7 ${message}` : ""}`,
2872
+ value: valueOf(item),
2873
+ tint: item.issue ? palette.error : void 0,
2874
+ onPress: () => openEvent(v, item.event)
2875
+ }
2876
+ );
2877
+ },
2878
+ // openEvent reads v.metrics at press time; rows only need new props when data changes.
2879
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2880
+ [t, v.metrics]
2881
+ );
2882
+ return /* @__PURE__ */ jsx10(
2883
+ LegendList4,
2884
+ {
2885
+ recycleItems: true,
2886
+ contentInsetAdjustmentBehavior: "automatic",
2887
+ style: styles7.list,
2888
+ data: v.timeline,
2889
+ keyExtractor: (item) => item.key,
2890
+ getItemType: (item) => item.kind,
2891
+ estimatedItemSize: 60,
2892
+ initialScrollOffset: v.offsets.current.timeline ?? 0,
2893
+ onScroll: (event) => {
2894
+ v.offsets.current.timeline = event.nativeEvent.contentOffset.y;
2895
+ },
2896
+ scrollEventThrottle: 100,
2897
+ onScrollBeginDrag: () => v.setHolding(true),
2898
+ ListEmptyComponent: /* @__PURE__ */ jsx10(Text7, { style: styles7.empty, children: t("empty") }),
2899
+ renderItem
2900
+ }
2901
+ );
2902
+ }
2903
+ var styles7 = StyleSheet8.create({
2904
+ list: { flex: 1 },
2905
+ header: {
2906
+ fontSize: 13,
2907
+ fontWeight: "600",
2908
+ color: apple.secondary,
2909
+ textTransform: "uppercase",
2910
+ paddingHorizontal: 16,
2911
+ paddingTop: 18,
2912
+ paddingBottom: 6,
2913
+ backgroundColor: apple.grouped
2914
+ },
2915
+ empty: {
2916
+ fontSize: 15,
2917
+ color: apple.secondary,
2918
+ textAlign: "center",
2919
+ padding: 32
2920
+ }
2921
+ });
2922
+
2923
+ // src/react/labels.ts
2924
+ var defaultLabels = {
2925
+ applyFilters: "Apply",
2926
+ title: "Performance",
2927
+ searchMatch: "Search match",
2928
+ explore: "Explore",
2929
+ profiles: "Profiles",
2930
+ spans: "Spans",
2931
+ events: "events",
2932
+ actions: "Actions",
2933
+ tools: "Tools",
2934
+ back: "Back",
2935
+ done: "Done",
2936
+ details: "Details",
2937
+ cancel: "Cancel",
2938
+ filters: "Filters",
2939
+ sort: "Sort",
2940
+ reset: "Reset",
2941
+ exploreSearch: "Search",
2942
+ metricSearch: "Search metrics",
2943
+ newest: "Newest",
2944
+ oldest: "Oldest",
2945
+ longest: "Longest",
2946
+ shortest: "Shortest",
2947
+ errors: "Most errors",
2948
+ count: "Most samples",
2949
+ name: "Name",
2950
+ p95: "Highest p95",
2951
+ source: "Source",
2952
+ outcome: "Outcome",
2953
+ success: "Success",
2954
+ error: "Error",
2955
+ cancelled: "Cancelled",
2956
+ interrupted: "Interrupted",
2957
+ noErrors: "No recorded errors",
2958
+ minDuration: "Minimum duration (ms)",
2959
+ from: "From (ms since recording)",
2960
+ to: "To (ms since recording)",
2961
+ correlation: "Exact correlation ID",
2962
+ invalidRange: "Enter nonnegative numbers, with From no later than To.",
2963
+ resultCount: "{{matching}} / {{loaded}} matching",
2964
+ traceRow: "{{spans}} spans \xB7 {{errors}} errors \xB7 {{source}}",
2965
+ noResults: "No matching events. Change filters or reset to see the retained recording.",
2966
+ uncorrelated: "Uncorrelated",
2967
+ uncorrelatedHint: "{{count}} uncorrelated events are also available in Spans and Marks.",
2968
+ pauseUpdates: "Freeze view",
2969
+ resumeUpdates: "Resume live view",
2970
+ newEvents: "{{count}} new events",
2971
+ detailFrozen: "Detail is held steady while capture continues. Return to load newer events.",
2972
+ expandAll: "Expand all",
2973
+ collapseAll: "Collapse all",
2974
+ missingParent: "Parent is outside retained history.",
2975
+ wholeTrace: "Show entire trace",
2976
+ similar: "Find similar spans",
2977
+ parent: "Parent",
2978
+ children: "Children",
2979
+ attributes: "Search attributes",
2980
+ evicted: "This event is outside retained history. Showing the last inspected snapshot.",
2981
+ all: "All",
2982
+ runtime: "Runtime",
2983
+ custom: "Custom",
2984
+ metricBrief: "p95 {{p95}} \xB7 n={{count}}",
2985
+ metricSortNotice: "All retained traces. Numeric sorting compares raw values; check units when comparing series.",
2986
+ exportAll: "Recording JSON (stops recording)",
2987
+ sharePerfetto: "Perfetto trace",
2988
+ replaceTitle: "Replace current recording?",
2989
+ replaceDescription: "Start a new recording and discard the currently retained history. Export first to keep a copy.",
2990
+ retainedScope: "Counts and statistics cover retained history. Pause updates freezes this view, not recording or profiling.",
2991
+ lostHistory: "Lost {{what}}; this history is incomplete.",
2992
+ lostSpans: "{{count}} spans",
2993
+ lostMarks: "{{count}} marks",
2994
+ lostMetrics: "{{count}} metric samples",
2995
+ slowest: "Slowest spans",
2996
+ errorsOnly: "Errors",
2997
+ profileSeparate: "CPU profiles are separate from trace JSON. Stop sampling to save and share the latest artifact.",
2998
+ noProfiler: "No CPU profiler is configured for this client.",
2999
+ eyebrow: "DEVELOPER TOOLS",
3000
+ close: "Close",
3001
+ recording: "Recording live",
3002
+ stopped: "Recording stopped",
3003
+ counters: "{{events}} events \xB7 {{active}} active \xB7 {{dropped}} dropped \xB7 {{bytes}} bytes",
3004
+ start: "Start recording",
3005
+ stop: "Stop recording",
3006
+ clear: "Clear",
3007
+ export: "Stop & export",
3008
+ retention: "Bounded native history \xB7 refreshes while open",
3009
+ traces: "Traces",
3010
+ search: "Search operation or correlation ID",
3011
+ traceSummary: "{{duration}} ms \xB7 {{events}} events \xB7 {{errors}} errors",
3012
+ empty: "No traces yet. Use the app to capture activity.",
3013
+ waterfall: "Span waterfall",
3014
+ noSpans: "No completed spans in this trace.",
3015
+ spanDetails: "Span {{id}} \xB7 parent {{parent}} \xB7 {{outcome}}",
3016
+ metrics: "Metrics",
3017
+ operations: "Operation outcomes",
3018
+ operationsShort: "Operations",
3019
+ operationStats: "{{count}} spans \xB7 {{errors}} errors \xB7 {{errorRate}} error rate \xB7 {{cancelled}} cancelled \xB7 {{interrupted}} interrupted",
3020
+ metricsScope: "All traces \xB7 app.ready.after_tracer_init measures tracer setup to app readiness, not TTI. duration: series are derived from completed spans. Statistics cover retained samples, not the whole session.",
3021
+ emptyMetrics: "No samples yet. Use the app or run the playground. App-ready timing appears after a fresh launch; TTI requires an explicit readiness definition.",
3022
+ metricStats: "Latest {{latest}} \xB7 median {{median}} \xB7 min {{min}} \xB7 max {{max}} \xB7 p95 {{p95}} \xB7 {{count}} samples",
3023
+ marks: "Marks",
3024
+ playground: "Instrumentation playground",
3025
+ playgroundDescription: "Check nested spans, concurrent operations with the same name, cancellation, repeated completion, errors and metrics.",
3026
+ run: "Run sample trace",
3027
+ profiling: "CPU profile",
3028
+ profilingDescription: "Capture a native profiler artifact alongside this recording.",
3029
+ startProfile: "Start CPU profile",
3030
+ stopProfile: "Stop CPU profile",
3031
+ shareProfile: "CPU profile",
3032
+ shareArtifact: "Share performance recording",
3033
+ sharingUnavailable: "File sharing is unavailable on this device.",
3034
+ cacheUnavailable: "The application cache is unavailable.",
3035
+ truncated: "Showing up to 100 traces, spans and marks, and 40 metric series. Search to narrow traces; export includes all retained events.",
3036
+ overview: "Summary",
3037
+ tabs: "Inspector views",
3038
+ recordingSummary: "Recording summary",
3039
+ summary: "{{traces}} traces \xB7 {{spans}} spans \xB7 {{marks}} marks \xB7 {{metrics}} metric samples loaded",
3040
+ session: "Session {{id}} \xB7 elapsed {{elapsed}} ms",
3041
+ metricWindow: "Showing {{displayed}} / {{retained}} loaded samples \xB7 {{start}}\u2013{{end}} ms since recording start",
3042
+ passed: "PASS",
3043
+ failed: "FAIL",
3044
+ scenarioResults: "Scenario results",
3045
+ scenarioNotice: "Checks inspect retained native events; dropped or expired samples can fail a check.",
3046
+ scenarioNested: "Explicit parent hierarchy",
3047
+ scenarioConcurrency: "Overlapping spans with the same name",
3048
+ scenarioCancelled: "Cancelled outcome",
3049
+ scenarioIdempotent: "Repeated completion records once",
3050
+ scenarioError: "Error outcome",
3051
+ scenarioMetrics: "Native metric values",
3052
+ openScenario: "Open scenario trace",
3053
+ timeline: "Timeline",
3054
+ issues: "Issues: {{count}}",
3055
+ noIssues: "Nothing over budget in retained history.",
3056
+ summaryScope: "Issues compare retained events with local budgets, not production percentiles. Tap a topic to explore it.",
3057
+ moreIssues: "{{count}} more in Explore",
3058
+ issueValue: "{{value}} ms \xB7 budget {{budget}} ms",
3059
+ notTracked: "Not tracked",
3060
+ quiet: "No events yet",
3061
+ topicStartup: "Startup",
3062
+ topicScreens: "Screens",
3063
+ topicNetwork: "Network",
3064
+ topicResponsiveness: "Responsiveness",
3065
+ topicErrors: "Errors",
3066
+ topicCustom: "Custom spans",
3067
+ headlineStartup: "App ready {{value}} after tracer init",
3068
+ headlineScreens: "{{count}} visits \xB7 p95 transition {{value}}",
3069
+ headlineNetwork: "{{count}} requests \xB7 p95 {{value}}",
3070
+ headlineResponsiveness: "{{count}} stalls over {{budget}} ms",
3071
+ headlineErrors: "{{count}} errors",
3072
+ headlineCustom: "{{count}} spans",
3073
+ hintStartup: "Call useAppReadyMetric(client, ready) from /react.",
3074
+ hintScreens: "Add createNavigationPlugin(navigationRef) from /plugins.",
3075
+ hintNetwork: "Add createNetworkPlugin() from /plugins.",
3076
+ hintResponsiveness: "Add createNativeMetricsPlugin(), Sentry frames, or runtimeMetrics: true.",
3077
+ hintErrors: "Add createErrorsPlugin() from /plugins.",
3078
+ hintCustom: "Use client.trace or native spans.",
3079
+ issueSlowStartup: "Slow startup",
3080
+ issueSlowScreen: "Slow screen",
3081
+ issueSlowRequest: "Slow request",
3082
+ issueFailedRequest: "Failed request",
3083
+ issueStall: "JS stall",
3084
+ issueError: "Error",
3085
+ beforeNavigation: "Before first screen",
3086
+ liveAllActivity: "All activity",
3087
+ liveInspector: "Inspector",
3088
+ liveRequests: "requests",
3089
+ liveStalls: "stalls",
3090
+ liveErrors: "errors",
3091
+ liveEmpty: "No activity on this screen yet. Use the app; events stream in here.",
3092
+ topicResources: "Resources",
3093
+ headlineResources: "CPU {{cpu}}% \xB7 {{memory}} MB",
3094
+ hintResources: "Add createNativeMetricsPlugin() from /plugins.",
3095
+ issueUiStall: "UI stall",
3096
+ issueFrozenFrames: "Frozen frames",
3097
+ liveFlag: "Flag",
3098
+ liveShare: "Share trace",
3099
+ liveFlagged: "Flagged",
3100
+ share: "Share",
3101
+ more: "More",
3102
+ shareTitle: "Share recording",
3103
+ startNew: "Start new recording",
3104
+ frozen: "Frozen",
3105
+ topics: "Topics",
3106
+ sortBy: "Sort: {{sort}}",
3107
+ filter: "Filter",
3108
+ range: "Range",
3109
+ stat_latest: "Latest",
3110
+ stat_median: "Median",
3111
+ stat_p95: "p95",
3112
+ stat_min: "Min",
3113
+ stat_max: "Max",
3114
+ samples: "Samples",
3115
+ duration: "Duration",
3116
+ time: "Time",
3117
+ spanId: "Span ID",
3118
+ correlationId: "Correlation ID",
3119
+ attributeSearch: "Search attributes"
3120
+ };
3121
+ var createTranslator = (labels) => (key, values) => (labels?.[key] ?? defaultLabels[key]).replace(
3122
+ /{{(\w+)}}/g,
3123
+ (_, name) => String(values?.[name] ?? "")
3124
+ );
3125
+
3126
+ // src/react/TraceInspector.tsx
3127
+ import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3128
+ var tabs = [
3129
+ { value: "overview", icon: "\u25C9", key: "overview", iconSize: 18 },
3130
+ { value: "timeline", icon: "\u2630", key: "timeline", iconSize: 22 },
3131
+ { value: "explore", icon: "\u2315", key: "explore", iconSize: 30 },
3132
+ { value: "metrics", icon: "\u25A5", key: "metrics", iconSize: 22 }
3133
+ ];
3134
+ var scenarioKey = {
3135
+ nested: "scenarioNested",
3136
+ concurrency: "scenarioConcurrency",
3137
+ cancelled: "scenarioCancelled",
3138
+ idempotent: "scenarioIdempotent",
3139
+ error: "scenarioError",
3140
+ metrics: "scenarioMetrics"
3141
+ };
3142
+ function TraceInspector({
3143
+ client,
3144
+ labels,
3145
+ translate,
3146
+ budgets
3147
+ }) {
3148
+ const v = useTraceViewer(client, budgets);
3149
+ const stallMs = budgets?.stallMs ?? defaultBudgets.stallMs;
3150
+ const t = translate ?? createTranslator(labels);
3151
+ useEffect3(() => {
3152
+ if (!v.visible) return;
3153
+ const entry = StatusBar.pushStackEntry({ barStyle: "dark-content" });
3154
+ return () => StatusBar.popStackEntry(entry);
3155
+ }, [v.visible]);
3156
+ const identity2 = v.detail?.kind === "span" ? v.detail.value.spanId : v.detail?.kind === "trace" ? v.detail.value.id : v.detail?.kind === "metric" ? v.detail.value.name : v.detail?.kind === "mark" ? String(v.detail.value.sequence) : "";
3157
+ return /* @__PURE__ */ jsx11(
3158
+ Modal3,
3159
+ {
3160
+ visible: v.visible,
3161
+ animationType: "slide",
3162
+ presentationStyle: "fullScreen",
3163
+ statusBarTranslucent: true,
3164
+ onRequestClose: v.detail ? v.back : client.close,
3165
+ children: /* @__PURE__ */ jsx11(SafeAreaProvider2, { children: /* @__PURE__ */ jsxs10(
3166
+ SafeAreaView3,
3167
+ {
3168
+ style: styles8.root,
3169
+ edges: ["top", "bottom", "left", "right"],
3170
+ children: [
3171
+ /* @__PURE__ */ jsx11(InspectorHeader, { v, t }),
3172
+ v.detail ? /* @__PURE__ */ jsx11(DetailView, { v, t }, `${v.details.length}:${identity2}`) : v.tab === "tools" ? /* @__PURE__ */ jsx11(ToolsView, { v, t, client }) : /* @__PURE__ */ jsx11(
3173
+ NativeTabs,
3174
+ {
3175
+ value: v.tab,
3176
+ onChange: v.setTab,
3177
+ tint: palette.accent,
3178
+ tabs: [
3179
+ {
3180
+ key: "overview",
3181
+ title: t("overview"),
3182
+ sfSymbol: "gauge.with.dots.needle.67percent",
3183
+ badge: v.issues.length ? String(v.issues.length) : void 0,
3184
+ render: () => /* @__PURE__ */ jsx11(SummaryView, { v, t, budgetMs: stallMs })
3185
+ },
3186
+ {
3187
+ key: "timeline",
3188
+ title: t("timeline"),
3189
+ sfSymbol: "list.bullet.below.rectangle",
3190
+ render: () => /* @__PURE__ */ jsx11(TimelineView, { v, t })
3191
+ },
3192
+ {
3193
+ key: "explore",
3194
+ title: t("explore"),
3195
+ sfSymbol: "magnifyingglass",
3196
+ render: () => /* @__PURE__ */ jsx11(ExplorerView, { v, t })
3197
+ },
3198
+ {
3199
+ key: "metrics",
3200
+ title: t("metrics"),
3201
+ sfSymbol: "chart.xyaxis.line",
3202
+ render: () => /* @__PURE__ */ jsx11(MetricsView, { v, t })
3203
+ }
3204
+ ],
3205
+ fallback: (content) => /* @__PURE__ */ jsxs10(Fragment4, { children: [
3206
+ content,
3207
+ /* @__PURE__ */ jsx11(
3208
+ TabBar,
3209
+ {
3210
+ items: tabs.map((tab) => ({ ...tab, label: t(tab.key) })),
3211
+ value: v.tab === "tools" ? "overview" : v.tab,
3212
+ onChange: v.setTab
3213
+ }
3214
+ )
3215
+ ] })
3216
+ }
3217
+ )
3218
+ ]
3219
+ }
3220
+ ) })
3221
+ }
3222
+ );
3223
+ }
3224
+ function ToolsView({
3225
+ v,
3226
+ t,
3227
+ client
3228
+ }) {
3229
+ const result = v.playgroundResult;
3230
+ return /* @__PURE__ */ jsxs10(ScrollView4, { contentContainerStyle: styles8.content, children: [
3231
+ /* @__PURE__ */ jsxs10(Section, { header: t("profiling"), footer: t("profileSeparate"), children: [
3232
+ client.canProfile ? /* @__PURE__ */ jsx11(
3233
+ Row,
3234
+ {
3235
+ icon: "\u25F7",
3236
+ title: t(v.profiling ? "stopProfile" : "startProfile"),
3237
+ subtitle: t("profilingDescription"),
3238
+ onPress: v.busy || !v.recording ? void 0 : v.toggleProfile,
3239
+ last: !v.profilePath
3240
+ }
3241
+ ) : /* @__PURE__ */ jsx11(Row, { title: t("noProfiler"), last: true }),
3242
+ v.profilePath && /* @__PURE__ */ jsx11(
3243
+ Row,
3244
+ {
3245
+ icon: "\u21EA",
3246
+ title: t("shareProfile"),
3247
+ subtitle: v.profilePath,
3248
+ onPress: client.canShareProfile ? v.shareProfile : void 0,
3249
+ last: true
3250
+ }
3251
+ )
3252
+ ] }),
3253
+ /* @__PURE__ */ jsxs10(Section, { header: t("playground"), footer: t("playgroundDescription"), children: [
3254
+ /* @__PURE__ */ jsx11(
3255
+ Row,
3256
+ {
3257
+ icon: "\u25B7",
3258
+ title: t("run"),
3259
+ onPress: v.busy || !v.recording ? void 0 : v.playground,
3260
+ last: !result
3261
+ }
3262
+ ),
3263
+ result?.checks.map((check) => /* @__PURE__ */ jsx11(
3264
+ Row,
3265
+ {
3266
+ icon: check.passed ? "\u2713" : "\u2715",
3267
+ iconColor: check.passed ? "#34c759" : palette.error,
3268
+ title: t(scenarioKey[check.id])
3269
+ },
3270
+ check.id
3271
+ )),
3272
+ result && /* @__PURE__ */ jsx11(Row, { title: t("openScenario"), onPress: v.openPlayground, last: true })
3273
+ ] })
3274
+ ] });
3275
+ }
3276
+ var styles8 = StyleSheet9.create({
3277
+ root: { flex: 1, backgroundColor: apple.grouped },
3278
+ content: { paddingBottom: 32 }
3279
+ });
3280
+
3281
+ // src/react/TraceOverlay.tsx
3282
+ import React9, {
3283
+ useContext,
3284
+ useMemo as useMemo4,
3285
+ useRef as useRef3,
3286
+ useState as useState7,
3287
+ useSyncExternalStore as useSyncExternalStore3
3288
+ } from "react";
3289
+ import {
3290
+ Animated,
3291
+ PanResponder,
3292
+ Pressable as Pressable6,
3293
+ ScrollView as ScrollView5,
3294
+ StyleSheet as StyleSheet10,
3295
+ Text as Text8,
3296
+ View as View9,
3297
+ useWindowDimensions
3298
+ } from "react-native";
3299
+ import {
3300
+ initialWindowMetrics,
3301
+ SafeAreaInsetsContext
3302
+ } from "react-native-safe-area-context";
3303
+
3304
+ // src/react/useLiveRecording.ts
3305
+ import { useEffect as useEffect4, useState as useState6 } from "react";
3306
+ function useLiveRecording(client, active, intervalMs = 1e3) {
3307
+ const [page, setPage] = useState6();
3308
+ useEffect4(() => {
3309
+ if (!active) return;
3310
+ let cancelled = false;
3311
+ let timer;
3312
+ let cached;
3313
+ let cachedRecording;
3314
+ const tick = async () => {
3315
+ try {
3316
+ const recording = client.getRecording();
3317
+ if (recording) {
3318
+ if (recording !== cachedRecording) {
3319
+ cached = void 0;
3320
+ cachedRecording = recording;
3321
+ }
3322
+ cached = await readSnapshot(recording, cached);
3323
+ if (!cancelled) setPage(cached);
3324
+ }
3325
+ } catch (error) {
3326
+ client.reportError(error);
3327
+ } finally {
3328
+ if (!cancelled) timer = setTimeout(tick, intervalMs);
3329
+ }
3330
+ };
3331
+ void tick();
3332
+ return () => {
3333
+ cancelled = true;
3334
+ clearTimeout(timer);
3335
+ };
3336
+ }, [client, active, intervalMs]);
3337
+ return page;
3338
+ }
3339
+
3340
+ // src/react/TraceOverlay.tsx
3341
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3342
+ var BUBBLE = 56;
3343
+ var PEEK = 150;
3344
+ var noInsets = { top: 0, right: 0, bottom: 0, left: 0 };
3345
+ var useInsets = () => useContext(SafeAreaInsetsContext) ?? initialWindowMetrics?.insets ?? noInsets;
3346
+ var round = (value) => value === void 0 ? "\u2014" : Math.round(value);
3347
+ function TraceOverlay({
3348
+ client,
3349
+ labels,
3350
+ translate,
3351
+ budgets,
3352
+ bottomOffset = 0
3353
+ }) {
3354
+ return (
3355
+ // Plain Views only: a native provider here would swallow touches on Android.
3356
+ /* @__PURE__ */ jsx12(View9, { style: StyleSheet10.absoluteFill, pointerEvents: "box-none", children: /* @__PURE__ */ jsx12(
3357
+ Overlay,
3358
+ {
3359
+ client,
3360
+ t: translate ?? createTranslator(labels),
3361
+ budgets: { ...defaultBudgets, ...budgets },
3362
+ bottomOffset
3363
+ }
3364
+ ) })
3365
+ );
3366
+ }
3367
+ function Overlay({
3368
+ client,
3369
+ t,
3370
+ budgets,
3371
+ bottomOffset
3372
+ }) {
3373
+ const snapshot = useSyncExternalStore3(
3374
+ client.subscribe,
3375
+ client.getSnapshot,
3376
+ client.getSnapshot
3377
+ );
3378
+ const [detent, setDetent] = useState7("closed");
3379
+ const page = useLiveRecording(
3380
+ client,
3381
+ !snapshot.visible && snapshot.recording,
3382
+ detent === "closed" ? 2e3 : 1e3
3383
+ );
3384
+ const { appReadyMs, screenMs, requestMs, stallMs } = budgets;
3385
+ const visit = useMemo4(() => {
3386
+ if (!page) return void 0;
3387
+ const { issues } = summarizeTopics(page, snapshot.collectors ?? [], {
3388
+ appReadyMs,
3389
+ screenMs,
3390
+ requestMs,
3391
+ stallMs
3392
+ });
3393
+ return currentVisit(buildTimeline(page, issues, 300));
3394
+ }, [page, snapshot.collectors, appReadyMs, screenMs, requestMs, stallMs]);
3395
+ const vitals = useMemo4(
3396
+ () => ({
3397
+ uiFps: page && latestMetric(page, "ui.fps"),
3398
+ jsFps: page && latestMetric(page, "js.frame_callback.rate"),
3399
+ cpu: page && latestMetric(page, "process.cpu"),
3400
+ memory: page && latestMetric(page, "process.memory")
3401
+ }),
3402
+ [page]
3403
+ );
3404
+ if (snapshot.visible || snapshot.disposed) return null;
3405
+ return detent === "closed" ? /* @__PURE__ */ jsx12(
3406
+ Bubble,
3407
+ {
3408
+ fps: vitals.uiFps ?? vitals.jsFps,
3409
+ native: vitals.uiFps !== void 0,
3410
+ issues: visit?.issues ?? 0,
3411
+ onPress: () => setDetent("half"),
3412
+ onLongPress: client.open
3413
+ }
3414
+ ) : /* @__PURE__ */ jsx12(
3415
+ Sheet,
3416
+ {
3417
+ bottomOffset,
3418
+ detent,
3419
+ setDetent,
3420
+ openInspector: () => {
3421
+ setDetent("closed");
3422
+ client.open();
3423
+ },
3424
+ nowMs: client.getRecording()?.getStats().nowMs,
3425
+ visit,
3426
+ vitals,
3427
+ recording: snapshot.recording,
3428
+ flag: () => client.flag(),
3429
+ share: client.canShare ? () => client.shareTrace("perfetto") : void 0,
3430
+ t
3431
+ }
3432
+ );
3433
+ }
3434
+ function Bubble({
3435
+ fps,
3436
+ native,
3437
+ issues,
3438
+ onPress,
3439
+ onLongPress
3440
+ }) {
3441
+ const { width, height } = useWindowDimensions();
3442
+ const insets = useInsets();
3443
+ const base = useRef3({ x: width - BUBBLE - 12, y: height * 0.55 });
3444
+ const position = useRef3(new Animated.ValueXY(base.current)).current;
3445
+ const handlers = useRef3({ onPress, onLongPress });
3446
+ handlers.current = { onPress, onLongPress };
3447
+ const responder = useMemo4(() => {
3448
+ let moved = false;
3449
+ let longPressed = false;
3450
+ let timer;
3451
+ return PanResponder.create({
3452
+ onStartShouldSetPanResponder: () => true,
3453
+ onPanResponderGrant: () => {
3454
+ moved = false;
3455
+ longPressed = false;
3456
+ timer = setTimeout(() => {
3457
+ longPressed = true;
3458
+ handlers.current.onLongPress();
3459
+ }, 500);
3460
+ },
3461
+ onPanResponderMove: (_, g) => {
3462
+ if (Math.abs(g.dx) + Math.abs(g.dy) > 6) {
3463
+ moved = true;
3464
+ clearTimeout(timer);
3465
+ }
3466
+ position.setValue({
3467
+ x: base.current.x + g.dx,
3468
+ y: base.current.y + g.dy
3469
+ });
3470
+ },
3471
+ onPanResponderRelease: (_, g) => {
3472
+ clearTimeout(timer);
3473
+ if (!moved) {
3474
+ if (!longPressed) handlers.current.onPress();
3475
+ return;
3476
+ }
3477
+ const x = base.current.x + g.dx;
3478
+ const y = base.current.y + g.dy;
3479
+ base.current = {
3480
+ x: x + BUBBLE / 2 < width / 2 ? 12 : width - BUBBLE - 12,
3481
+ y: Math.min(
3482
+ Math.max(y, insets.top + 8),
3483
+ height - BUBBLE - insets.bottom - 8
3484
+ )
3485
+ };
3486
+ Animated.spring(position, {
3487
+ toValue: base.current,
3488
+ useNativeDriver: false
3489
+ }).start();
3490
+ },
3491
+ onPanResponderTerminate: () => {
3492
+ clearTimeout(timer);
3493
+ position.setValue(base.current);
3494
+ }
3495
+ });
3496
+ }, [position, width, height, insets.top, insets.bottom]);
3497
+ const alert = issues > 0;
3498
+ return /* @__PURE__ */ jsxs11(
3499
+ Animated.View,
3500
+ {
3501
+ ...responder.panHandlers,
3502
+ accessibilityRole: "button",
3503
+ accessibilityLabel: "Performance overlay",
3504
+ accessibilityHint: "Tap for live activity, long press for the full inspector",
3505
+ style: [
3506
+ styles9.bubble,
3507
+ alert && styles9.bubbleAlert,
3508
+ { transform: position.getTranslateTransform() }
3509
+ ],
3510
+ children: [
3511
+ /* @__PURE__ */ jsx12(
3512
+ Glass,
3513
+ {
3514
+ style: [StyleSheet10.absoluteFill, styles9.bubbleGlass],
3515
+ fallbackColor: palette.bg,
3516
+ interactive: true
3517
+ }
3518
+ ),
3519
+ /* @__PURE__ */ jsx12(Text8, { style: [styles9.bubbleValue, alert && { color: palette.error }], children: fps === void 0 ? "\u2014" : Math.round(fps) }),
3520
+ /* @__PURE__ */ jsx12(Text8, { style: styles9.bubbleUnit, children: alert ? `${issues} \u26A0` : native ? "UI fps" : "JS fps" })
3521
+ ]
3522
+ }
3523
+ );
3524
+ }
3525
+ function Sheet({
3526
+ detent,
3527
+ setDetent,
3528
+ openInspector,
3529
+ nowMs,
3530
+ visit,
3531
+ vitals,
3532
+ recording,
3533
+ flag,
3534
+ share,
3535
+ t,
3536
+ bottomOffset
3537
+ }) {
3538
+ const { height } = useWindowDimensions();
3539
+ const insets = useInsets();
3540
+ const [flagged, setFlagged] = useState7(false);
3541
+ const inset = bottomOffset > 0 ? 0 : insets.bottom;
3542
+ const half = Math.round(height * 0.45) + inset;
3543
+ const peek = PEEK + inset;
3544
+ const offsetFor = (value) => value === "half" ? 0 : value === "peek" ? half - peek : half + bottomOffset;
3545
+ const translate = useRef3(new Animated.Value(half + bottomOffset)).current;
3546
+ const settle = (value) => Animated.spring(translate, {
3547
+ toValue: offsetFor(value),
3548
+ useNativeDriver: true,
3549
+ bounciness: 0
3550
+ }).start(() => value === "closed" && setDetent("closed"));
3551
+ const current = useRef3(detent);
3552
+ current.current = detent;
3553
+ React9.useEffect(() => {
3554
+ settle(detent);
3555
+ }, [detent, half]);
3556
+ const responder = useMemo4(
3557
+ () => PanResponder.create({
3558
+ onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dy) > 4,
3559
+ onPanResponderMove: (_, g) => translate.setValue(Math.max(-80, offsetFor(current.current) + g.dy)),
3560
+ onPanResponderRelease: (_, g) => {
3561
+ const at = offsetFor(current.current) + g.dy;
3562
+ if (at < -60) {
3563
+ settle(current.current);
3564
+ openInspector();
3565
+ } else if (at > half - peek / 2 || g.vy > 1.5) settle("closed");
3566
+ else {
3567
+ const next = at < (half - peek) / 2 ? "half" : "peek";
3568
+ if (next === current.current) settle(next);
3569
+ else setDetent(next);
3570
+ }
3571
+ }
3572
+ }),
3573
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3574
+ [half, peek]
3575
+ );
3576
+ const elapsed = nowMs !== void 0 && visit?.sinceMs !== void 0 ? (nowMs - visit.sinceMs) / 1e3 : void 0;
3577
+ const latest = visit?.events[0];
3578
+ return /* @__PURE__ */ jsxs11(
3579
+ Animated.View,
3580
+ {
3581
+ style: [
3582
+ styles9.sheet,
3583
+ { height: half, paddingBottom: inset, bottom: bottomOffset },
3584
+ { transform: [{ translateY: translate }] }
3585
+ ],
3586
+ children: [
3587
+ /* @__PURE__ */ jsx12(
3588
+ Glass,
3589
+ {
3590
+ style: [StyleSheet10.absoluteFill, styles9.sheetGlass],
3591
+ fallbackColor: palette.bg
3592
+ }
3593
+ ),
3594
+ /* @__PURE__ */ jsxs11(View9, { ...responder.panHandlers, style: styles9.grabArea, children: [
3595
+ /* @__PURE__ */ jsx12(View9, { style: styles9.grabber }),
3596
+ /* @__PURE__ */ jsxs11(View9, { style: styles9.row, children: [
3597
+ /* @__PURE__ */ jsxs11(Text8, { style: styles9.screen, numberOfLines: 1, children: [
3598
+ recording ? "\u25CF" : "\u25CB",
3599
+ " ",
3600
+ visit?.screen ?? t("liveAllActivity"),
3601
+ elapsed !== void 0 && /* @__PURE__ */ jsxs11(Text8, { style: styles9.muted, children: [
3602
+ " \xB7 ",
3603
+ elapsed.toFixed(0),
3604
+ " s"
3605
+ ] })
3606
+ ] }),
3607
+ /* @__PURE__ */ jsx12(
3608
+ Pressable6,
3609
+ {
3610
+ accessibilityRole: "button",
3611
+ onPress: () => setDetent(detent === "half" ? "peek" : "half"),
3612
+ style: styles9.chip,
3613
+ children: /* @__PURE__ */ jsx12(Text8, { style: styles9.chipText, children: detent === "half" ? "\u25BE" : "\u25B4" })
3614
+ }
3615
+ ),
3616
+ /* @__PURE__ */ jsx12(
3617
+ Pressable6,
3618
+ {
3619
+ accessibilityRole: "button",
3620
+ onPress: openInspector,
3621
+ style: styles9.chip,
3622
+ children: /* @__PURE__ */ jsx12(Text8, { style: styles9.chipText, children: t("liveInspector") })
3623
+ }
3624
+ ),
3625
+ /* @__PURE__ */ jsx12(
3626
+ Pressable6,
3627
+ {
3628
+ accessibilityRole: "button",
3629
+ accessibilityLabel: t("close"),
3630
+ onPress: () => settle("closed"),
3631
+ style: styles9.chip,
3632
+ children: /* @__PURE__ */ jsx12(Text8, { style: styles9.chipText, children: "\u2715" })
3633
+ }
3634
+ )
3635
+ ] }),
3636
+ /* @__PURE__ */ jsxs11(View9, { style: styles9.stats, children: [
3637
+ /* @__PURE__ */ jsx12(Stat, { label: "UI fps", value: round(vitals.uiFps) }),
3638
+ /* @__PURE__ */ jsx12(Stat, { label: "JS fps", value: round(vitals.jsFps) }),
3639
+ /* @__PURE__ */ jsx12(Stat, { label: "CPU %", value: round(vitals.cpu) }),
3640
+ /* @__PURE__ */ jsx12(Stat, { label: "MB", value: round(vitals.memory) })
3641
+ ] }),
3642
+ /* @__PURE__ */ jsxs11(View9, { style: styles9.stats, children: [
3643
+ /* @__PURE__ */ jsx12(Stat, { label: t("liveRequests"), value: visit?.requests ?? 0 }),
3644
+ /* @__PURE__ */ jsx12(Stat, { label: t("liveStalls"), value: visit?.stalls ?? 0, alert: true }),
3645
+ /* @__PURE__ */ jsx12(Stat, { label: t("liveErrors"), value: visit?.errors ?? 0, alert: true }),
3646
+ /* @__PURE__ */ jsx12(
3647
+ Pressable6,
3648
+ {
3649
+ accessibilityRole: "button",
3650
+ onPress: () => {
3651
+ flag();
3652
+ setFlagged(true);
3653
+ setTimeout(() => setFlagged(false), 1200);
3654
+ },
3655
+ style: styles9.action,
3656
+ children: /* @__PURE__ */ jsxs11(Text8, { style: styles9.actionText, children: [
3657
+ "\u2691 ",
3658
+ t(flagged ? "liveFlagged" : "liveFlag")
3659
+ ] })
3660
+ }
3661
+ ),
3662
+ share && /* @__PURE__ */ jsx12(
3663
+ Pressable6,
3664
+ {
3665
+ accessibilityRole: "button",
3666
+ onPress: () => void share(),
3667
+ style: styles9.action,
3668
+ children: /* @__PURE__ */ jsxs11(Text8, { style: styles9.actionText, children: [
3669
+ "\u21EA ",
3670
+ t("liveShare")
3671
+ ] })
3672
+ }
3673
+ )
3674
+ ] })
3675
+ ] }),
3676
+ detent === "peek" ? latest && /* @__PURE__ */ jsxs11(Text8, { style: styles9.muted, numberOfLines: 1, children: [
3677
+ topicGlyph[latest.topic].icon,
3678
+ " ",
3679
+ latest.event.name
3680
+ ] }) : /* @__PURE__ */ jsxs11(ScrollView5, { style: styles9.list, children: [
3681
+ !visit?.events.length && /* @__PURE__ */ jsx12(Text8, { style: [styles9.muted, styles9.empty], children: t("liveEmpty") }),
3682
+ visit?.events.slice(0, 100).map((item) => /* @__PURE__ */ jsxs11(View9, { style: styles9.event, children: [
3683
+ /* @__PURE__ */ jsx12(
3684
+ Text8,
3685
+ {
3686
+ style: [styles9.glyph, { color: topicGlyph[item.topic].color }],
3687
+ children: topicGlyph[item.topic].icon
3688
+ }
3689
+ ),
3690
+ /* @__PURE__ */ jsx12(
3691
+ Text8,
3692
+ {
3693
+ style: [styles9.eventName, item.issue && styles9.alertText],
3694
+ numberOfLines: 1,
3695
+ children: item.event.name
3696
+ }
3697
+ ),
3698
+ /* @__PURE__ */ jsx12(Text8, { style: [styles9.value, item.issue && styles9.alertText], children: "durationMs" in item.event ? `${item.event.durationMs.toFixed(0)} ms` : "value" in item.event ? `${item.event.value.toFixed(0)} ${item.event.unit}` : "" }),
3699
+ /* @__PURE__ */ jsxs11(Text8, { style: styles9.time, children: [
3700
+ "+",
3701
+ ((item.event.timestampMs - (visit.sinceMs ?? 0)) / 1e3).toFixed(1),
3702
+ "s"
3703
+ ] })
3704
+ ] }, item.key))
3705
+ ] })
3706
+ ]
3707
+ }
3708
+ );
3709
+ }
3710
+ function Stat({
3711
+ label,
3712
+ value,
3713
+ alert
3714
+ }) {
3715
+ const hot = alert && Number(value) > 0;
3716
+ return /* @__PURE__ */ jsxs11(View9, { style: styles9.stat, children: [
3717
+ /* @__PURE__ */ jsx12(Text8, { style: [styles9.statValue, hot && styles9.alertText], children: value }),
3718
+ /* @__PURE__ */ jsx12(Text8, { style: styles9.statLabel, children: label })
3719
+ ] });
3720
+ }
3721
+ var styles9 = StyleSheet10.create({
3722
+ bubble: {
3723
+ position: "absolute",
3724
+ width: BUBBLE,
3725
+ height: BUBBLE,
3726
+ borderRadius: BUBBLE / 2,
3727
+ overflow: "hidden",
3728
+ borderWidth: StyleSheet10.hairlineWidth * 2,
3729
+ borderColor: palette.line,
3730
+ alignItems: "center",
3731
+ justifyContent: "center",
3732
+ shadowColor: "#000",
3733
+ shadowOpacity: 0.15,
3734
+ shadowRadius: 10,
3735
+ shadowOffset: { width: 0, height: 4 },
3736
+ elevation: 6
3737
+ },
3738
+ bubbleGlass: { borderRadius: BUBBLE / 2 },
3739
+ sheetGlass: { borderTopLeftRadius: 22, borderTopRightRadius: 22 },
3740
+ bubbleAlert: { borderColor: palette.error, borderWidth: 2 },
3741
+ bubbleValue: {
3742
+ color: palette.text,
3743
+ fontSize: 17,
3744
+ fontWeight: "600",
3745
+ fontVariant: ["tabular-nums"]
3746
+ },
3747
+ bubbleUnit: { color: palette.muted, fontSize: 10, marginTop: -2 },
3748
+ sheet: {
3749
+ position: "absolute",
3750
+ left: 0,
3751
+ right: 0,
3752
+ bottom: 0,
3753
+ borderTopLeftRadius: 22,
3754
+ borderTopRightRadius: 22,
3755
+ overflow: "hidden",
3756
+ borderTopWidth: StyleSheet10.hairlineWidth,
3757
+ borderColor: palette.line,
3758
+ paddingHorizontal: 14,
3759
+ shadowColor: "#000",
3760
+ shadowOpacity: 0.12,
3761
+ shadowRadius: 16,
3762
+ shadowOffset: { width: 0, height: -4 },
3763
+ elevation: 12
3764
+ },
3765
+ grabArea: { paddingBottom: 8 },
3766
+ grabber: {
3767
+ alignSelf: "center",
3768
+ width: 36,
3769
+ height: 5,
3770
+ borderRadius: 3,
3771
+ backgroundColor: palette.line,
3772
+ marginTop: 6,
3773
+ marginBottom: 8
3774
+ },
3775
+ row: { flexDirection: "row", alignItems: "center", gap: 6 },
3776
+ screen: { flex: 1, color: palette.text, fontSize: 15, fontWeight: "600" },
3777
+ muted: { color: palette.muted, fontSize: 13, fontWeight: "400" },
3778
+ chip: {
3779
+ paddingHorizontal: 10,
3780
+ paddingVertical: 5,
3781
+ borderRadius: 6,
3782
+ backgroundColor: palette.panel
3783
+ },
3784
+ chipText: { color: palette.accent, fontSize: 13, fontWeight: "500" },
3785
+ stats: {
3786
+ flexDirection: "row",
3787
+ marginTop: 10,
3788
+ borderTopWidth: StyleSheet10.hairlineWidth,
3789
+ borderColor: palette.line,
3790
+ paddingTop: 8
3791
+ },
3792
+ stat: { flex: 1, alignItems: "center" },
3793
+ action: {
3794
+ paddingHorizontal: 10,
3795
+ paddingVertical: 6,
3796
+ borderRadius: 7,
3797
+ backgroundColor: palette.panel,
3798
+ marginLeft: 6,
3799
+ alignSelf: "center"
3800
+ },
3801
+ actionText: { color: palette.accent, fontSize: 13, fontWeight: "500" },
3802
+ statValue: {
3803
+ color: palette.text,
3804
+ fontSize: 17,
3805
+ fontWeight: "600",
3806
+ fontVariant: ["tabular-nums"]
3807
+ },
3808
+ statLabel: { color: palette.muted, fontSize: 11 },
3809
+ list: { flex: 1 },
3810
+ empty: { paddingVertical: 12 },
3811
+ event: {
3812
+ flexDirection: "row",
3813
+ alignItems: "center",
3814
+ gap: 8,
3815
+ paddingVertical: 7,
3816
+ borderBottomWidth: StyleSheet10.hairlineWidth,
3817
+ borderColor: palette.line
3818
+ },
3819
+ glyph: { width: 18, textAlign: "center", color: palette.muted },
3820
+ eventName: { flex: 1, color: palette.text, fontSize: 13 },
3821
+ value: {
3822
+ color: palette.text,
3823
+ fontSize: 13,
3824
+ fontVariant: ["tabular-nums"]
3825
+ },
3826
+ time: {
3827
+ width: 52,
3828
+ textAlign: "right",
3829
+ color: palette.muted,
3830
+ fontSize: 12,
3831
+ fontVariant: ["tabular-nums"]
3832
+ },
3833
+ alertText: { color: palette.error }
3834
+ });
3835
+
3836
+ // src/react/useAppReadyMetric.ts
3837
+ import { useEffect as useEffect5 } from "react";
3838
+ function useAppReadyMetric(client, ready) {
3839
+ useEffect5(() => {
3840
+ if (!ready) return;
3841
+ const frame = requestAnimationFrame(() => client.reportAppReady());
3842
+ return () => cancelAnimationFrame(frame);
3843
+ }, [client, ready]);
3844
+ }
3845
+ export {
3846
+ TraceInspector,
3847
+ TraceOverlay,
3848
+ defaultLabels,
3849
+ useAppReadyMetric
3850
+ };
3851
+ //# sourceMappingURL=react.mjs.map