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