@buoy-gg/network 7.0.15 → 7.0.16

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 (107) hide show
  1. package/lib/commonjs/index.web.js +395 -0
  2. package/lib/commonjs/network/components/DataViewer.web.js +879 -0
  3. package/lib/commonjs/network/components/NetworkCopyScreen.web.js +48 -0
  4. package/lib/commonjs/network/components/NetworkCopySettingsView.web.js +1242 -0
  5. package/lib/commonjs/network/components/NetworkDetailScreen.web.js +101 -0
  6. package/lib/commonjs/network/components/NetworkDetailStepper.web.js +296 -0
  7. package/lib/commonjs/network/components/NetworkEventDetailView.web.js +1498 -0
  8. package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +802 -0
  9. package/lib/commonjs/network/components/NetworkFilterScreen.web.js +55 -0
  10. package/lib/commonjs/network/components/NetworkFilterViewV3.web.js +1310 -0
  11. package/lib/commonjs/network/components/NetworkHeaderLive.web.js +604 -0
  12. package/lib/commonjs/network/components/NetworkListScreen.js +2 -1
  13. package/lib/commonjs/network/components/NetworkListScreen.web.js +607 -0
  14. package/lib/commonjs/network/components/NetworkModal.js +12 -3
  15. package/lib/commonjs/network/components/NetworkNumberPrompt.web.js +232 -0
  16. package/lib/commonjs/network/components/NetworkOverrideEditor.web.js +942 -0
  17. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.web.js +133 -0
  18. package/lib/commonjs/network/components/NetworkOverridesButton.web.js +143 -0
  19. package/lib/commonjs/network/components/NetworkOverridesScreen.js +160 -126
  20. package/lib/commonjs/network/components/NetworkOverridesScreen.web.js +830 -0
  21. package/lib/commonjs/network/components/NetworkSavedScreen.web.js +459 -0
  22. package/lib/commonjs/network/components/response-editor/Explorer.web.js +1136 -0
  23. package/lib/commonjs/network/hooks/useNetworkEventList.js +12 -2
  24. package/lib/commonjs/network/overrides/applyToXhr.js +276 -15
  25. package/lib/commonjs/network/utils/generateNetworkCopyText.js +32 -5
  26. package/lib/commonjs/network/utils/networkEventStore.js +12 -4
  27. package/lib/commonjs/network/utils/networkListener.js +1 -16
  28. package/lib/module/index.web.js +81 -0
  29. package/lib/module/network/components/DataViewer.web.js +875 -0
  30. package/lib/module/network/components/NetworkCopyScreen.web.js +44 -0
  31. package/lib/module/network/components/NetworkCopySettingsView.web.js +1237 -0
  32. package/lib/module/network/components/NetworkDetailScreen.web.js +97 -0
  33. package/lib/module/network/components/NetworkDetailStepper.web.js +294 -0
  34. package/lib/module/network/components/NetworkEventDetailView.web.js +1496 -0
  35. package/lib/module/network/components/NetworkEventItemCompact.web.js +800 -0
  36. package/lib/module/network/components/NetworkFilterScreen.web.js +51 -0
  37. package/lib/module/network/components/NetworkFilterViewV3.web.js +1306 -0
  38. package/lib/module/network/components/NetworkHeaderLive.web.js +598 -0
  39. package/lib/module/network/components/NetworkListScreen.js +2 -1
  40. package/lib/module/network/components/NetworkListScreen.web.js +605 -0
  41. package/lib/module/network/components/NetworkModal.js +12 -3
  42. package/lib/module/network/components/NetworkNumberPrompt.web.js +230 -0
  43. package/lib/module/network/components/NetworkOverrideEditor.web.js +939 -0
  44. package/lib/module/network/components/NetworkOverrideHeaderButton.web.js +131 -0
  45. package/lib/module/network/components/NetworkOverridesButton.web.js +141 -0
  46. package/lib/module/network/components/NetworkOverridesScreen.js +161 -127
  47. package/lib/module/network/components/NetworkOverridesScreen.web.js +826 -0
  48. package/lib/module/network/components/NetworkSavedScreen.web.js +457 -0
  49. package/lib/module/network/components/response-editor/Explorer.web.js +1133 -0
  50. package/lib/module/network/hooks/useNetworkEventList.js +12 -2
  51. package/lib/module/network/overrides/applyToXhr.js +275 -15
  52. package/lib/module/network/utils/generateNetworkCopyText.js +28 -1
  53. package/lib/module/network/utils/networkEventStore.js +12 -4
  54. package/lib/module/network/utils/networkListener.js +1 -16
  55. package/lib/typescript/index.web.d.ts +56 -0
  56. package/lib/typescript/index.web.d.ts.map +1 -0
  57. package/lib/typescript/network/components/DataViewer.web.d.ts +78 -0
  58. package/lib/typescript/network/components/DataViewer.web.d.ts.map +1 -0
  59. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts +27 -0
  60. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts.map +1 -0
  61. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts +68 -0
  62. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts.map +1 -0
  63. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts +45 -0
  64. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts.map +1 -0
  65. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts +40 -0
  66. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts.map +1 -0
  67. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts +67 -0
  68. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts.map +1 -0
  69. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts +58 -0
  70. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +1 -0
  71. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts +28 -0
  72. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts.map +1 -0
  73. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts +74 -0
  74. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts.map +1 -0
  75. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts +112 -0
  76. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +1 -0
  77. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
  78. package/lib/typescript/network/components/NetworkListScreen.web.d.ts +63 -0
  79. package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +1 -0
  80. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  81. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts +38 -0
  82. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts.map +1 -0
  83. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts +43 -0
  84. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts.map +1 -0
  85. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts +32 -0
  86. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts.map +1 -0
  87. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts +26 -0
  88. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts.map +1 -0
  89. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts +9 -0
  90. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -1
  91. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts +69 -0
  92. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts.map +1 -0
  93. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts +67 -0
  94. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts.map +1 -0
  95. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts +56 -0
  96. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts.map +1 -0
  97. package/lib/typescript/network/hooks/useNetworkEventList.d.ts +10 -0
  98. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -1
  99. package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts.map +1 -1
  100. package/lib/typescript/network/overrides/applyToXhr.d.ts +40 -14
  101. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -1
  102. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +34 -1
  103. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -1
  104. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  105. package/lib/typescript/network/utils/networkListener.d.ts +0 -1
  106. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  107. package/package.json +2 -2
@@ -0,0 +1,1496 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * NetworkEventDetailView.web — DOM twin of NetworkEventDetailView.tsx.
5
+ *
6
+ * The request DETAIL inspector: method/status/duration header row, URL
7
+ * breakdown (with collapsible query parameters), error box, timing card
8
+ * (Started + Sent/Received + the two real timing-phase bars), collapsible
9
+ * Request/Response Headers, Request/Response Body (JSON trees, response
10
+ * expanded on arrival), and the Filter Options section — same section order,
11
+ * labels, defaults and styling as native. The data layer is REUSED, not
12
+ * forked: `useNetworkBodyResolver` (plain React context), `formatGraphQLDisplay`
13
+ * and the `NetworkEvent`/`NetworkTimings` types are the same modules native
14
+ * uses; `showBodies` carries the same settle-window contract (see the native
15
+ * file's prop doc) with the same fixed-height `BodyDeferred` stand-in.
16
+ *
17
+ * shared-ui RN chrome is replicated as DOM with provenance (web consumers
18
+ * resolve `@buoy-gg/shared-ui` to a minimal shim):
19
+ * - `macOSColors` — inlined verbatim subset from
20
+ * shared/src/ui/gameUI/constants/macOSDesignSystemColors.ts.
21
+ * - Icons — inline lucide SVGs (shared-ui's icons are lucide re-exports).
22
+ * - `formatBytes` / `formatDuration` — copied verbatim from
23
+ * shared/src/utils/formatting/dataFormatting.ts (not in the web shim).
24
+ * - `formatHttpStatus` — copied verbatim from
25
+ * shared/src/utils/formatting/httpFormatting.ts; its `gameUIColors` are the
26
+ * macOS theme, so the color table below carries the same resolved values.
27
+ * - `formatRelativeTime` — copied verbatim from
28
+ * shared/src/utils/time/formatRelativeTime.ts.
29
+ * - `DataViewer` / `InlineCopyButton` — DOM twins in ./DataViewer.web.
30
+ *
31
+ * Intentional deviations (all logged):
32
+ * - License gating isn't part of the web port: the view renders the Pro path
33
+ * (`isPro = true` everywhere native branches) — copy buttons are real
34
+ * clipboard copies, never upgrade prompts — and shared-ui's `UpgradeModal`
35
+ * is therefore not mounted (`useFeatureGate` lives behind @buoy-gg/license,
36
+ * outside the web port).
37
+ * - Copy affordances use `navigator.clipboard` via the CopyButton twin; the
38
+ * full-request copy keeps native's LAZY function value so no markdown is
39
+ * built per render.
40
+ * - Relative time ("12s ago") refreshes on re-render only — native mounts a
41
+ * `TickProvider` at the modal root; the web port's live-event subscription
42
+ * re-renders an open detail on every store update, which covers it in
43
+ * practice for in-flight requests.
44
+ *
45
+ * @platform React DOM Web
46
+ */
47
+
48
+ import { memo, useEffect, useMemo, useState } from "react";
49
+ import { formatGraphQLDisplay } from "../utils/formatGraphQLVariables";
50
+ import { useNetworkBodyResolver } from "../sync/networkBodyResolver";
51
+ import { DataViewer, InlineCopyButton } from "./DataViewer.web";
52
+
53
+ // =============================
54
+ // Native palettes (provenance)
55
+ // =============================
56
+
57
+ /** macOSColors subset — shared/src/ui/gameUI/constants/macOSDesignSystemColors.ts, verbatim. */
58
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
59
+ const macOSColors = {
60
+ background: {
61
+ base: "#0A0A0C",
62
+ card: "#1A1A1C",
63
+ hover: "#1D1D1F",
64
+ input: "#26262A"
65
+ },
66
+ border: {
67
+ default: "#2D2D2F"
68
+ },
69
+ text: {
70
+ primary: "#F5F5F7",
71
+ secondary: "#A1A1A6",
72
+ muted: "#8E8E93"
73
+ },
74
+ semantic: {
75
+ success: "#34C759",
76
+ error: "#FF453A",
77
+ errorBackground: "rgba(255, 69, 58, 0.15)",
78
+ warning: "#FFEB3B",
79
+ warningBackground: "rgba(255, 235, 59, 0.15)",
80
+ info: "#00B8E6",
81
+ infoBackground: "rgba(0, 184, 230, 0.1)"
82
+ }
83
+ };
84
+
85
+ /**
86
+ * Colour for the icons that merely LABEL a section (Sent/Received, the JSON
87
+ * body glyphs) — same rationale as native.
88
+ */
89
+ const NEUTRAL_ICON = macOSColors.semantic.success;
90
+
91
+ // =============================
92
+ // Formatting utils (provenance: shared/src/utils — not in the web shim)
93
+ // =============================
94
+
95
+ /** shared/src/utils/formatting/dataFormatting.ts `formatBytes`, verbatim. */
96
+ function formatBytes(bytes) {
97
+ if (bytes === undefined || bytes === null) return "N/A";
98
+ if (bytes === 0) return "0 B";
99
+ const k = 1024;
100
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
101
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
102
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
103
+ }
104
+
105
+ /** shared/src/utils/formatting/dataFormatting.ts `formatDuration`, verbatim. */
106
+ function formatDuration(ms) {
107
+ if (ms === undefined || ms === null) return "N/A";
108
+ if (ms < 1000) return `${ms}ms`;
109
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
110
+ if (ms < 3600000) {
111
+ const minutes = Math.floor(ms / 60000);
112
+ const seconds = Math.floor(ms % 60000 / 1000);
113
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
114
+ }
115
+ const hours = Math.floor(ms / 3600000);
116
+ const minutes = Math.floor(ms % 3600000 / 60000);
117
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
118
+ }
119
+
120
+ /** shared/src/utils/time/formatRelativeTime.ts, verbatim. */
121
+ function formatRelativeTime(timestamp, currentTime = Date.now()) {
122
+ const timestampMs = timestamp instanceof Date ? timestamp.getTime() : timestamp;
123
+ const seconds = Math.floor((currentTime - timestampMs) / 1000);
124
+ if (seconds < 0) return "just now";
125
+ if (seconds < 60) return seconds === 0 ? "just now" : `${seconds}s ago`;
126
+ const minutes = Math.floor(seconds / 60);
127
+ if (minutes < 60) return `${minutes}m ago`;
128
+ const hours = Math.floor(minutes / 60);
129
+ if (hours < 24) return `${hours}h ago`;
130
+ const days = Math.floor(hours / 24);
131
+ return `${days}d ago`;
132
+ }
133
+
134
+ /**
135
+ * shared/src/utils/formatting/httpFormatting.ts `formatHttpStatus`, verbatim —
136
+ * its `gameUIColors` resolve to the macOS theme's semantic values (the same
137
+ * `macOSColors.semantic` palette inlined above; muted = text.muted).
138
+ */
139
+ function formatHttpStatus(status) {
140
+ if (status >= 100 && status < 200) {
141
+ return {
142
+ text: String(status),
143
+ color: macOSColors.semantic.info,
144
+ meaning: "Informational"
145
+ };
146
+ }
147
+ if (status >= 200 && status < 300) {
148
+ const meanings = {
149
+ 200: "OK",
150
+ 201: "Created",
151
+ 202: "Accepted",
152
+ 204: "No Content",
153
+ 206: "Partial Content"
154
+ };
155
+ return {
156
+ text: String(status),
157
+ color: macOSColors.semantic.success,
158
+ meaning: meanings[status] || "Success"
159
+ };
160
+ }
161
+ if (status >= 300 && status < 400) {
162
+ const meanings = {
163
+ 301: "Moved Permanently",
164
+ 302: "Found",
165
+ 303: "See Other",
166
+ 304: "Not Modified",
167
+ 307: "Temporary Redirect",
168
+ 308: "Permanent Redirect"
169
+ };
170
+ return {
171
+ text: String(status),
172
+ color: macOSColors.semantic.warning,
173
+ meaning: meanings[status] || "Redirect"
174
+ };
175
+ }
176
+ if (status >= 400 && status < 500) {
177
+ const meanings = {
178
+ 400: "Bad Request",
179
+ 401: "Unauthorized",
180
+ 402: "Payment Required",
181
+ 403: "Forbidden",
182
+ 404: "Not Found",
183
+ 405: "Method Not Allowed",
184
+ 408: "Request Timeout",
185
+ 409: "Conflict",
186
+ 410: "Gone",
187
+ 422: "Unprocessable Entity",
188
+ 429: "Too Many Requests"
189
+ };
190
+ return {
191
+ text: String(status),
192
+ color: macOSColors.semantic.error,
193
+ meaning: meanings[status] || "Client Error"
194
+ };
195
+ }
196
+ if (status >= 500) {
197
+ const meanings = {
198
+ 500: "Internal Server Error",
199
+ 501: "Not Implemented",
200
+ 502: "Bad Gateway",
201
+ 503: "Service Unavailable",
202
+ 504: "Gateway Timeout",
203
+ 505: "HTTP Version Not Supported"
204
+ };
205
+ return {
206
+ text: String(status),
207
+ color: macOSColors.semantic.error,
208
+ meaning: meanings[status] || "Server Error"
209
+ };
210
+ }
211
+ return {
212
+ text: String(status),
213
+ color: macOSColors.text.muted,
214
+ meaning: "Unknown"
215
+ };
216
+ }
217
+
218
+ // =============================
219
+ // Inline lucide icons (shared-ui icons are lucide re-exports)
220
+ // =============================
221
+
222
+ function Svg({
223
+ size = 14,
224
+ color = "currentColor",
225
+ children
226
+ }) {
227
+ return /*#__PURE__*/_jsx("svg", {
228
+ width: size,
229
+ height: size,
230
+ viewBox: "0 0 24 24",
231
+ fill: "none",
232
+ stroke: color,
233
+ strokeWidth: 2,
234
+ strokeLinecap: "round",
235
+ strokeLinejoin: "round",
236
+ style: {
237
+ flexShrink: 0,
238
+ display: "block"
239
+ },
240
+ children: children
241
+ });
242
+ }
243
+ const Clock = p => /*#__PURE__*/_jsxs(Svg, {
244
+ ...p,
245
+ children: [/*#__PURE__*/_jsx("circle", {
246
+ cx: "12",
247
+ cy: "12",
248
+ r: "10"
249
+ }), /*#__PURE__*/_jsx("polyline", {
250
+ points: "12 6 12 12 16 14"
251
+ })]
252
+ });
253
+ const Upload = p => /*#__PURE__*/_jsxs(Svg, {
254
+ ...p,
255
+ children: [/*#__PURE__*/_jsx("path", {
256
+ d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
257
+ }), /*#__PURE__*/_jsx("polyline", {
258
+ points: "17 8 12 3 7 8"
259
+ }), /*#__PURE__*/_jsx("line", {
260
+ x1: "12",
261
+ x2: "12",
262
+ y1: "3",
263
+ y2: "15"
264
+ })]
265
+ });
266
+ const Download = p => /*#__PURE__*/_jsxs(Svg, {
267
+ ...p,
268
+ children: [/*#__PURE__*/_jsx("path", {
269
+ d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
270
+ }), /*#__PURE__*/_jsx("polyline", {
271
+ points: "7 10 12 15 17 10"
272
+ }), /*#__PURE__*/_jsx("line", {
273
+ x1: "12",
274
+ x2: "12",
275
+ y1: "15",
276
+ y2: "3"
277
+ })]
278
+ });
279
+ const AlertCircle = p => /*#__PURE__*/_jsxs(Svg, {
280
+ ...p,
281
+ children: [/*#__PURE__*/_jsx("circle", {
282
+ cx: "12",
283
+ cy: "12",
284
+ r: "10"
285
+ }), /*#__PURE__*/_jsx("line", {
286
+ x1: "12",
287
+ x2: "12",
288
+ y1: "8",
289
+ y2: "12"
290
+ }), /*#__PURE__*/_jsx("line", {
291
+ x1: "12",
292
+ x2: "12.01",
293
+ y1: "16",
294
+ y2: "16"
295
+ })]
296
+ });
297
+ const ChevronDown = p => /*#__PURE__*/_jsx(Svg, {
298
+ ...p,
299
+ children: /*#__PURE__*/_jsx("path", {
300
+ d: "m6 9 6 6 6-6"
301
+ })
302
+ });
303
+ const ChevronUp = p => /*#__PURE__*/_jsx(Svg, {
304
+ ...p,
305
+ children: /*#__PURE__*/_jsx("path", {
306
+ d: "m18 15-6-6-6 6"
307
+ })
308
+ });
309
+ const Lock = p => /*#__PURE__*/_jsxs(Svg, {
310
+ ...p,
311
+ children: [/*#__PURE__*/_jsx("rect", {
312
+ width: "18",
313
+ height: "11",
314
+ x: "3",
315
+ y: "11",
316
+ rx: "2",
317
+ ry: "2"
318
+ }), /*#__PURE__*/_jsx("path", {
319
+ d: "M7 11V7a5 5 0 0 1 10 0v4"
320
+ })]
321
+ });
322
+ const Unlock = p => /*#__PURE__*/_jsxs(Svg, {
323
+ ...p,
324
+ children: [/*#__PURE__*/_jsx("rect", {
325
+ width: "18",
326
+ height: "11",
327
+ x: "3",
328
+ y: "11",
329
+ rx: "2",
330
+ ry: "2"
331
+ }), /*#__PURE__*/_jsx("path", {
332
+ d: "M7 11V7a5 5 0 0 1 9.9-1"
333
+ })]
334
+ });
335
+ const FileJson = p => /*#__PURE__*/_jsxs(Svg, {
336
+ ...p,
337
+ children: [/*#__PURE__*/_jsx("path", {
338
+ d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"
339
+ }), /*#__PURE__*/_jsx("path", {
340
+ d: "M14 2v4a2 2 0 0 0 2 2h4"
341
+ }), /*#__PURE__*/_jsx("path", {
342
+ d: "M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1"
343
+ }), /*#__PURE__*/_jsx("path", {
344
+ d: "M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1"
345
+ })]
346
+ });
347
+ const Filter = p => /*#__PURE__*/_jsx(Svg, {
348
+ ...p,
349
+ children: /*#__PURE__*/_jsx("polygon", {
350
+ points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"
351
+ })
352
+ });
353
+ const Globe = p => /*#__PURE__*/_jsxs(Svg, {
354
+ ...p,
355
+ children: [/*#__PURE__*/_jsx("circle", {
356
+ cx: "12",
357
+ cy: "12",
358
+ r: "10"
359
+ }), /*#__PURE__*/_jsx("path", {
360
+ d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"
361
+ }), /*#__PURE__*/_jsx("path", {
362
+ d: "M2 12h20"
363
+ })]
364
+ });
365
+ const Link = p => /*#__PURE__*/_jsxs(Svg, {
366
+ ...p,
367
+ children: [/*#__PURE__*/_jsx("path", {
368
+ d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
369
+ }), /*#__PURE__*/_jsx("path", {
370
+ d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
371
+ })]
372
+ });
373
+ const FlaskConical = p => /*#__PURE__*/_jsxs(Svg, {
374
+ ...p,
375
+ children: [/*#__PURE__*/_jsx("path", {
376
+ d: "M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2"
377
+ }), /*#__PURE__*/_jsx("path", {
378
+ d: "M8.5 2h7"
379
+ }), /*#__PURE__*/_jsx("path", {
380
+ d: "M7 16h10"
381
+ })]
382
+ });
383
+
384
+ // =============================
385
+ // View (structure mirrors the native file 1:1)
386
+ // =============================
387
+
388
+ /** Hoisted so the defaults don't mint a new Set/function per render (native). */
389
+ const EMPTY_PATTERNS = new Set();
390
+ const noop = () => {};
391
+ /**
392
+ * The measurable slice of a request's lifetime, as proportional bars.
393
+ * Renders NOTHING when the interceptor couldn't measure it — same "absent,
394
+ * not zeroed" rule as native. Only two phases, on purpose (see native).
395
+ */
396
+ const TimingPhases = ({
397
+ timings,
398
+ total
399
+ }) => {
400
+ // Guard the divisor: a sub-millisecond request would otherwise divide by 0.
401
+ const span = Math.max(1, total);
402
+ const phases = [{
403
+ label: "Waiting (TTFB)",
404
+ hint: "incl. queue, DNS, TLS",
405
+ value: timings.ttfb,
406
+ color: macOSColors.semantic.warning
407
+ }, {
408
+ label: "Content download",
409
+ hint: undefined,
410
+ value: timings.download,
411
+ color: macOSColors.semantic.success
412
+ }];
413
+ return /*#__PURE__*/_jsx("div", {
414
+ style: styles.phaseList,
415
+ children: phases.map(phase => /*#__PURE__*/_jsxs("div", {
416
+ style: styles.phaseRow,
417
+ children: [/*#__PURE__*/_jsxs("div", {
418
+ style: styles.phaseLabelGroup,
419
+ children: [/*#__PURE__*/_jsx("div", {
420
+ style: styles.phaseLabel,
421
+ children: phase.label
422
+ }), phase.hint ? /*#__PURE__*/_jsx("div", {
423
+ style: styles.phaseHint,
424
+ children: phase.hint
425
+ }) : null]
426
+ }), /*#__PURE__*/_jsx("div", {
427
+ style: styles.phaseTrack,
428
+ children: /*#__PURE__*/_jsx("div", {
429
+ style: {
430
+ ...styles.phaseBar,
431
+ backgroundColor: phase.color,
432
+ // Floor so a 1ms phase is a visible tick; ceiling because ttfb
433
+ // can exceed event.duration (measured at different points).
434
+ width: `${Math.min(100, Math.max(1, phase.value / span * 100))}%`
435
+ }
436
+ })
437
+ }), /*#__PURE__*/_jsx("span", {
438
+ style: styles.phaseValue,
439
+ children: formatDuration(phase.value)
440
+ })]
441
+ }, phase.label))
442
+ });
443
+ };
444
+
445
+ /**
446
+ * Stand-in for a body tree while the prev/next cursor is still moving.
447
+ * Fixed-height nothing — must cost ~0 and must not resize the scroll view as
448
+ * the real tree swaps in (native rationale).
449
+ */
450
+ const BodyDeferred = () => /*#__PURE__*/_jsx("div", {
451
+ style: styles.bodyDeferred
452
+ });
453
+
454
+ // Component for collapsible sections matching Sentry style
455
+ const CollapsibleSection = ({
456
+ title,
457
+ icon,
458
+ children,
459
+ defaultOpen = false,
460
+ count
461
+ }) => {
462
+ const [isOpen, setIsOpen] = useState(defaultOpen);
463
+ return /*#__PURE__*/_jsxs("div", {
464
+ style: styles.collapsibleSection,
465
+ children: [/*#__PURE__*/_jsxs("button", {
466
+ type: "button",
467
+ style: styles.collapsibleHeader,
468
+ onClick: () => setIsOpen(!isOpen),
469
+ "aria-expanded": isOpen,
470
+ children: [/*#__PURE__*/_jsxs("span", {
471
+ style: styles.collapsibleTitle,
472
+ children: [icon, /*#__PURE__*/_jsx("span", {
473
+ style: styles.collapsibleTitleText,
474
+ children: title
475
+ }), count !== undefined ? /*#__PURE__*/_jsx("span", {
476
+ style: styles.collapsibleCount,
477
+ children: count
478
+ }) : null]
479
+ }), isOpen ? /*#__PURE__*/_jsx(ChevronUp, {
480
+ size: 16,
481
+ color: macOSColors.text.secondary
482
+ }) : /*#__PURE__*/_jsx(ChevronDown, {
483
+ size: 16,
484
+ color: macOSColors.text.secondary
485
+ })]
486
+ }), isOpen ? /*#__PURE__*/_jsx("div", {
487
+ style: styles.collapsibleContent,
488
+ children: children
489
+ }) : null]
490
+ });
491
+ };
492
+
493
+ // URL breakdown component matching Sentry style
494
+ const UrlBreakdown = ({
495
+ url,
496
+ operationName,
497
+ graphqlVariables,
498
+ requestClient
499
+ }) => {
500
+ const parseUrl = urlString => {
501
+ try {
502
+ const urlObj = new URL(urlString);
503
+ const protocol = String(urlObj.protocol || "");
504
+ const host = String(urlObj.host || "");
505
+ const isSecure = protocol === "https:";
506
+ let pathname = String(urlObj.pathname || "");
507
+
508
+ // GraphQL requests format with arrow notation, matching React Query's
509
+ // key display: "GetPokemon › Sandshrew" (same pure util as native).
510
+ if (requestClient === "graphql" && operationName) {
511
+ const graphqlDisplay = formatGraphQLDisplay(operationName, graphqlVariables);
512
+ pathname = `${pathname} (${graphqlDisplay})`;
513
+ } else if (operationName) {
514
+ // Non-GraphQL requests with operation names (e.g., gRPC)
515
+ pathname = `${pathname} (${operationName})`;
516
+ }
517
+ const params = {};
518
+ const searchParams = urlObj.searchParams;
519
+ if (searchParams && typeof searchParams.forEach === "function") {
520
+ searchParams.forEach((value, key) => {
521
+ params[key] = value;
522
+ });
523
+ }
524
+ return {
525
+ protocol: protocol.replace(":", ""),
526
+ host,
527
+ pathname,
528
+ params: Object.keys(params).length > 0 ? params : null,
529
+ isSecure
530
+ };
531
+ } catch {
532
+ return {
533
+ protocol: "",
534
+ host: url,
535
+ pathname: "",
536
+ params: null,
537
+ isSecure: false
538
+ };
539
+ }
540
+ };
541
+
542
+ /**
543
+ * MUST be memoized — `params` is handed to DataViewer as its `data` prop,
544
+ * which the tree keys on by IDENTITY (same trap the native file documents).
545
+ */
546
+ const urlParts = useMemo(() => parseUrl(url),
547
+ // parseUrl closes over the graphql/operation props, which are what change
548
+ // the derived pathname.
549
+ // eslint-disable-next-line react-hooks/exhaustive-deps
550
+ [url, requestClient, operationName, graphqlVariables]);
551
+ const [showParams, setShowParams] = useState(false);
552
+ return /*#__PURE__*/_jsxs("div", {
553
+ style: styles.urlBreakdown,
554
+ children: [/*#__PURE__*/_jsxs("div", {
555
+ style: styles.urlRow,
556
+ children: [urlParts.isSecure ? /*#__PURE__*/_jsx(Lock, {
557
+ size: 12,
558
+ color: macOSColors.semantic.success
559
+ }) : /*#__PURE__*/_jsx(Unlock, {
560
+ size: 12,
561
+ color: macOSColors.semantic.warning
562
+ }), /*#__PURE__*/_jsx("span", {
563
+ style: styles.urlDomain,
564
+ children: urlParts.host
565
+ }), /*#__PURE__*/_jsxs("span", {
566
+ style: styles.urlProtocol,
567
+ children: ["(", urlParts.protocol.toUpperCase(), ")"]
568
+ }), /*#__PURE__*/_jsx(InlineCopyButton, {
569
+ value: url,
570
+ buttonStyle: styles.copyButton
571
+ })]
572
+ }), /*#__PURE__*/_jsx("div", {
573
+ style: styles.urlPathRow,
574
+ children: /*#__PURE__*/_jsxs("span", {
575
+ style: styles.urlPath,
576
+ children: [urlParts.pathname, urlParts.params ? /*#__PURE__*/_jsxs("span", {
577
+ style: styles.urlQueryString,
578
+ children: ["?", new URLSearchParams(urlParts.params).toString()]
579
+ }) : null]
580
+ })
581
+ }), urlParts.params ? /*#__PURE__*/_jsxs("div", {
582
+ style: styles.queryParamsCollapsible,
583
+ children: [/*#__PURE__*/_jsxs("button", {
584
+ type: "button",
585
+ style: styles.queryParamsToggle,
586
+ onClick: () => setShowParams(!showParams),
587
+ "aria-expanded": showParams,
588
+ children: [/*#__PURE__*/_jsx("span", {
589
+ style: styles.queryParamsToggleText,
590
+ children: "Query Parameters"
591
+ }), /*#__PURE__*/_jsx("span", {
592
+ style: {
593
+ ...styles.collapsibleCount,
594
+ flex: 1,
595
+ textAlign: "left"
596
+ },
597
+ children: Object.keys(urlParts.params).length
598
+ }), showParams ? /*#__PURE__*/_jsx(ChevronUp, {
599
+ size: 14,
600
+ color: macOSColors.text.secondary
601
+ }) : /*#__PURE__*/_jsx(ChevronDown, {
602
+ size: 14,
603
+ color: macOSColors.text.secondary
604
+ })]
605
+ }), showParams ? /*#__PURE__*/_jsx(DataViewer, {
606
+ title: "",
607
+ data: urlParts.params,
608
+ showTypeFilter: true,
609
+ rawMode: true,
610
+ initialExpanded: true
611
+ }) : null]
612
+ }) : null]
613
+ });
614
+ };
615
+
616
+ /**
617
+ * Detailed inspector for a single network event — Sentry-style layout with
618
+ * collapsible sections and quick copy affordances.
619
+ *
620
+ * MEMOIZED for the same two render paths as native: `useLiveNetworkEvent`
621
+ * replays the same event object on re-subscribe, and host-root state changes
622
+ * re-render the shell with the detail body unchanged.
623
+ */
624
+ export const NetworkEventDetailView = /*#__PURE__*/memo(function NetworkEventDetailView({
625
+ event,
626
+ ignoredPatterns = EMPTY_PATTERNS,
627
+ onTogglePattern = noop,
628
+ showBodies = true
629
+ }) {
630
+ const status = event.status ? formatHttpStatus(event.status) : null;
631
+ const isPending = !event.status && !event.error;
632
+
633
+ // Large bodies are omitted from synced snapshots and fetched on demand
634
+ // through the resolver (plain React context — reused, not forked). In a
635
+ // webview port there is normally no resolver and bodies are always inline,
636
+ // so this is a no-op there — exactly like on the device.
637
+ const resolveBody = useNetworkBodyResolver();
638
+ const requestBodyOmitted = event.requestData === undefined && (event.requestSize ?? 0) > 0;
639
+ const responseBodyOmitted = event.responseData === undefined && (event.responseSize ?? 0) > 0;
640
+ const [resolvedBody, setResolvedBody] = useState(null);
641
+ const [isLoadingBody, setIsLoadingBody] = useState(false);
642
+ useEffect(() => {
643
+ setResolvedBody(null);
644
+ if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted) {
645
+ setIsLoadingBody(false);
646
+ return;
647
+ }
648
+ let cancelled = false;
649
+ setIsLoadingBody(true);
650
+ resolveBody(event.id).then(body => {
651
+ if (!cancelled) setResolvedBody(body);
652
+ }).catch(() => {}).finally(() => {
653
+ if (!cancelled) setIsLoadingBody(false);
654
+ });
655
+ return () => {
656
+ cancelled = true;
657
+ };
658
+ }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted]);
659
+
660
+ // Effective bodies: inline data when present, otherwise the lazily resolved
661
+ // body. `null`/`undefined` both collapse to undefined ("no body").
662
+ const requestData = event.requestData ?? resolvedBody?.requestData ?? undefined;
663
+ const responseData = event.responseData ?? resolvedBody?.responseData ?? undefined;
664
+
665
+ // Generate full request details for copying (resolved LAZILY at press time —
666
+ // see the copy button below).
667
+ const getFullRequestDetails = () => {
668
+ const requestDetails = {
669
+ method: event.method,
670
+ url: event.url,
671
+ status: event.status,
672
+ statusText: event.statusText,
673
+ timestamp: new Date(event.timestamp).toISOString(),
674
+ duration: event.duration ? `${event.duration}ms` : "N/A",
675
+ requestHeaders: event.requestHeaders,
676
+ requestData,
677
+ responseHeaders: event.responseHeaders,
678
+ responseData,
679
+ requestSize: event.requestSize ? formatBytes(event.requestSize) : "N/A",
680
+ responseSize: event.responseSize ? formatBytes(event.responseSize) : "N/A",
681
+ error: event.error,
682
+ client: event.requestClient
683
+ };
684
+ return `# Network Request Details
685
+
686
+ ## Request
687
+ - **Method:** ${requestDetails.method}
688
+ - **URL:** ${requestDetails.url}
689
+ - **Client:** ${requestDetails.client || "N/A"}
690
+ - **Timestamp:** ${requestDetails.timestamp}
691
+
692
+ ## Response
693
+ - **Status:** ${requestDetails.status || "Pending"}${requestDetails.statusText ? ` (${requestDetails.statusText})` : ""}
694
+ - **Duration:** ${requestDetails.duration}
695
+ - **Request Size:** ${requestDetails.requestSize}
696
+ - **Response Size:** ${requestDetails.responseSize}
697
+ ${requestDetails.error ? `- **Error:** ${requestDetails.error}` : ""}
698
+
699
+ ## Request Headers
700
+ \`\`\`json
701
+ ${JSON.stringify(requestDetails.requestHeaders, null, 2)}
702
+ \`\`\`
703
+
704
+ ## Request Data
705
+ \`\`\`json
706
+ ${JSON.stringify(requestDetails.requestData, null, 2)}
707
+ \`\`\`
708
+
709
+ ## Response Headers
710
+ \`\`\`json
711
+ ${JSON.stringify(requestDetails.responseHeaders, null, 2)}
712
+ \`\`\`
713
+
714
+ ## Response Data
715
+ \`\`\`json
716
+ ${JSON.stringify(requestDetails.responseData, null, 2)}
717
+ \`\`\`
718
+ `;
719
+ };
720
+ return /*#__PURE__*/_jsxs("div", {
721
+ style: styles.container,
722
+ children: [/*#__PURE__*/_jsxs("div", {
723
+ style: styles.requestDetailsSection,
724
+ children: [/*#__PURE__*/_jsxs("div", {
725
+ style: styles.httpHeader,
726
+ children: [/*#__PURE__*/_jsx("span", {
727
+ style: styles.httpMethodBadge,
728
+ children: /*#__PURE__*/_jsx("span", {
729
+ style: styles.httpMethod,
730
+ children: event.method
731
+ })
732
+ }), event.status ? /*#__PURE__*/_jsx("span", {
733
+ style: {
734
+ ...styles.httpStatusBadge,
735
+ backgroundColor: `${status?.color}20`
736
+ },
737
+ children: /*#__PURE__*/_jsxs("span", {
738
+ style: {
739
+ ...styles.httpStatusText,
740
+ color: status?.color
741
+ },
742
+ children: [status?.text, " ", status?.meaning]
743
+ })
744
+ }) : isPending ? /*#__PURE__*/_jsxs("span", {
745
+ style: styles.pendingBadge,
746
+ children: [/*#__PURE__*/_jsx(Clock, {
747
+ size: 10,
748
+ color: macOSColors.semantic.warning
749
+ }), /*#__PURE__*/_jsx("span", {
750
+ style: styles.pendingBadgeText,
751
+ children: "Pending"
752
+ })]
753
+ }) : null, event.duration ? /*#__PURE__*/_jsxs("span", {
754
+ style: styles.httpDuration,
755
+ children: [/*#__PURE__*/_jsx(Clock, {
756
+ size: 10,
757
+ color: macOSColors.text.muted
758
+ }), /*#__PURE__*/_jsx("span", {
759
+ style: styles.httpDurationText,
760
+ children: formatDuration(event.duration)
761
+ })]
762
+ }) : null, event.override ? /*#__PURE__*/_jsxs("span", {
763
+ style: styles.overrideBadge,
764
+ children: [/*#__PURE__*/_jsx(FlaskConical, {
765
+ size: 10,
766
+ color: macOSColors.semantic.warning
767
+ }), /*#__PURE__*/_jsx("span", {
768
+ style: styles.overrideBadgeText,
769
+ children: event.override.ruleName || "Overridden"
770
+ })]
771
+ }) : null, /*#__PURE__*/_jsx(InlineCopyButton, {
772
+ value: getFullRequestDetails,
773
+ buttonStyle: styles.copyFullButton
774
+ })]
775
+ }), /*#__PURE__*/_jsx(UrlBreakdown, {
776
+ url: event.url,
777
+ requestData: requestData,
778
+ operationName: event.operationName,
779
+ graphqlVariables: event.graphqlVariables,
780
+ requestClient: event.requestClient
781
+ }), event.error ? /*#__PURE__*/_jsxs("div", {
782
+ style: styles.errorBox,
783
+ children: [/*#__PURE__*/_jsx(AlertCircle, {
784
+ size: 12,
785
+ color: macOSColors.semantic.error
786
+ }), /*#__PURE__*/_jsx("span", {
787
+ style: styles.errorText,
788
+ children: event.error
789
+ })]
790
+ }) : null]
791
+ }), /*#__PURE__*/_jsxs("div", {
792
+ style: styles.timingSection,
793
+ children: [/*#__PURE__*/_jsxs("div", {
794
+ style: styles.timingRow,
795
+ children: [/*#__PURE__*/_jsx(Clock, {
796
+ size: 12,
797
+ color: macOSColors.text.secondary
798
+ }), /*#__PURE__*/_jsx("span", {
799
+ style: styles.timingLabel,
800
+ children: "Started:"
801
+ }), /*#__PURE__*/_jsx("span", {
802
+ style: styles.timingValue,
803
+ children: formatRelativeTime(event.timestamp)
804
+ }), /*#__PURE__*/_jsxs("span", {
805
+ style: styles.timingExact,
806
+ children: ["(", new Date(event.timestamp).toLocaleTimeString(), ")"]
807
+ }), event.requestSize || event.responseSize ? /*#__PURE__*/_jsxs("span", {
808
+ style: styles.sizeRow,
809
+ children: [event.requestSize !== undefined ? /*#__PURE__*/_jsxs("span", {
810
+ style: styles.sizeItem,
811
+ children: [/*#__PURE__*/_jsx(Upload, {
812
+ size: 10,
813
+ color: macOSColors.semantic.info
814
+ }), /*#__PURE__*/_jsx("span", {
815
+ style: styles.sizeLabel,
816
+ children: "Sent:"
817
+ }), /*#__PURE__*/_jsx("span", {
818
+ style: styles.sizeValue,
819
+ children: formatBytes(event.requestSize)
820
+ })]
821
+ }) : null, event.responseSize !== undefined ? /*#__PURE__*/_jsxs("span", {
822
+ style: styles.sizeItem,
823
+ children: [/*#__PURE__*/_jsx(Download, {
824
+ size: 10,
825
+ color: NEUTRAL_ICON
826
+ }), /*#__PURE__*/_jsx("span", {
827
+ style: styles.sizeLabel,
828
+ children: "Received:"
829
+ }), /*#__PURE__*/_jsx("span", {
830
+ style: styles.sizeValue,
831
+ children: formatBytes(event.responseSize)
832
+ })]
833
+ }) : null]
834
+ }) : null]
835
+ }), event.timings ? /*#__PURE__*/_jsxs(_Fragment, {
836
+ children: [/*#__PURE__*/_jsx("div", {
837
+ style: styles.phaseDivider
838
+ }), /*#__PURE__*/_jsx(TimingPhases, {
839
+ timings: event.timings,
840
+ total: event.duration ?? event.timings.ttfb + event.timings.download
841
+ })]
842
+ }) : null]
843
+ }), /*#__PURE__*/_jsx(CollapsibleSection, {
844
+ title: "Request Headers",
845
+ icon: /*#__PURE__*/_jsx(Upload, {
846
+ size: 14,
847
+ color: macOSColors.semantic.info
848
+ }),
849
+ defaultOpen: false,
850
+ count: Object.keys(event.requestHeaders).length,
851
+ children: Object.keys(event.requestHeaders).length > 0 ? /*#__PURE__*/_jsx("div", {
852
+ style: styles.dataViewerContainer,
853
+ children: /*#__PURE__*/_jsx(DataViewer, {
854
+ title: "",
855
+ data: event.requestHeaders,
856
+ showTypeFilter: true,
857
+ rawMode: true,
858
+ initialExpanded: true
859
+ })
860
+ }) : /*#__PURE__*/_jsx("div", {
861
+ style: styles.emptyText,
862
+ children: "No request headers"
863
+ })
864
+ }), /*#__PURE__*/_jsx(CollapsibleSection, {
865
+ title: "Response Headers",
866
+ icon: /*#__PURE__*/_jsx(Download, {
867
+ size: 14,
868
+ color: NEUTRAL_ICON
869
+ }),
870
+ defaultOpen: false,
871
+ count: Object.keys(event.responseHeaders).length,
872
+ children: Object.keys(event.responseHeaders).length > 0 ? /*#__PURE__*/_jsx("div", {
873
+ style: styles.dataViewerContainer,
874
+ children: /*#__PURE__*/_jsx(DataViewer, {
875
+ title: "",
876
+ data: event.responseHeaders,
877
+ showTypeFilter: true,
878
+ rawMode: true,
879
+ initialExpanded: true
880
+ })
881
+ }) : /*#__PURE__*/_jsx("div", {
882
+ style: styles.emptyText,
883
+ children: "No response headers yet"
884
+ })
885
+ }), requestData !== undefined ? /*#__PURE__*/_jsx(CollapsibleSection, {
886
+ title: "Request Body",
887
+ icon: /*#__PURE__*/_jsx(FileJson, {
888
+ size: 14,
889
+ color: macOSColors.semantic.info
890
+ }),
891
+ defaultOpen: false,
892
+ children: /*#__PURE__*/_jsx("div", {
893
+ style: styles.dataViewerContainer,
894
+ children: showBodies ? /*#__PURE__*/_jsx(DataViewer, {
895
+ title: "",
896
+ data: requestData,
897
+ showTypeFilter: true,
898
+ rawMode: true,
899
+ initialExpanded: true
900
+ }) : /*#__PURE__*/_jsx(BodyDeferred, {})
901
+ })
902
+ }) : requestBodyOmitted ? /*#__PURE__*/_jsx(CollapsibleSection, {
903
+ title: "Request Body",
904
+ icon: /*#__PURE__*/_jsx(FileJson, {
905
+ size: 14,
906
+ color: macOSColors.semantic.info
907
+ }),
908
+ defaultOpen: true,
909
+ children: /*#__PURE__*/_jsx("div", {
910
+ style: styles.emptyText,
911
+ children: isLoadingBody ? `Loading request body (${formatBytes(event.requestSize ?? 0)})…` : "Request body unavailable"
912
+ })
913
+ }) : null, responseData !== undefined ? /*#__PURE__*/_jsx(CollapsibleSection, {
914
+ title: "Response Body",
915
+ icon: /*#__PURE__*/_jsx(FileJson, {
916
+ size: 14,
917
+ color: NEUTRAL_ICON
918
+ })
919
+ // The body is what you opened the row FOR — expanded on arrival,
920
+ // everything else closed (native).
921
+ ,
922
+ defaultOpen: true,
923
+ children: /*#__PURE__*/_jsx("div", {
924
+ style: styles.dataViewerContainer,
925
+ children: showBodies ? /*#__PURE__*/_jsx(DataViewer, {
926
+ title: "",
927
+ data: responseData,
928
+ showTypeFilter: true,
929
+ rawMode: true,
930
+ initialExpanded: true
931
+ }) : /*#__PURE__*/_jsx(BodyDeferred, {})
932
+ })
933
+ }) : responseBodyOmitted ? /*#__PURE__*/_jsx(CollapsibleSection, {
934
+ title: "Response Body",
935
+ icon: /*#__PURE__*/_jsx(FileJson, {
936
+ size: 14,
937
+ color: NEUTRAL_ICON
938
+ }),
939
+ defaultOpen: true,
940
+ children: /*#__PURE__*/_jsx("div", {
941
+ style: styles.emptyText,
942
+ children: isLoadingBody ? `Loading response body (${formatBytes(event.responseSize ?? 0)})…` : "Response body unavailable"
943
+ })
944
+ }) : null, /*#__PURE__*/_jsx(CollapsibleSection, {
945
+ title: "Filter Options",
946
+ icon: /*#__PURE__*/_jsx(Filter, {
947
+ size: 14,
948
+ color: macOSColors.semantic.warning
949
+ }),
950
+ defaultOpen: false,
951
+ children: /*#__PURE__*/_jsx("div", {
952
+ style: styles.filterOptionsContainer,
953
+ children: (() => {
954
+ let domain = "";
955
+ let urlPath = "";
956
+ try {
957
+ const url = new URL(event.url);
958
+ domain = url.hostname;
959
+ urlPath = url.pathname;
960
+ } catch {
961
+ urlPath = event.url;
962
+ }
963
+ const isDomainIgnored = ignoredPatterns.has(domain);
964
+ const isUrlIgnored = ignoredPatterns.has(urlPath);
965
+ return /*#__PURE__*/_jsxs(_Fragment, {
966
+ children: [/*#__PURE__*/_jsxs("button", {
967
+ type: "button",
968
+ style: {
969
+ ...styles.filterOption,
970
+ ...(isDomainIgnored ? styles.filterOptionActive : null)
971
+ },
972
+ onClick: () => domain && onTogglePattern(domain),
973
+ children: [/*#__PURE__*/_jsxs("span", {
974
+ style: styles.filterOptionLeft,
975
+ children: [/*#__PURE__*/_jsx(Globe, {
976
+ size: 16,
977
+ color: isDomainIgnored ? macOSColors.semantic.warning : macOSColors.text.muted
978
+ }), /*#__PURE__*/_jsxs("span", {
979
+ style: styles.filterOptionContent,
980
+ children: [/*#__PURE__*/_jsx("span", {
981
+ style: styles.filterOptionLabel,
982
+ children: "Ignore Domain"
983
+ }), /*#__PURE__*/_jsx("span", {
984
+ style: styles.filterOptionValue,
985
+ children: domain || "N/A"
986
+ })]
987
+ })]
988
+ }), /*#__PURE__*/_jsx("span", {
989
+ style: {
990
+ ...styles.filterToggle,
991
+ ...(isDomainIgnored ? styles.filterToggleActive : null)
992
+ },
993
+ children: /*#__PURE__*/_jsx("span", {
994
+ style: {
995
+ ...styles.filterToggleText,
996
+ ...(isDomainIgnored ? styles.filterToggleTextActive : null)
997
+ },
998
+ children: isDomainIgnored ? "IGNORED" : "IGNORE"
999
+ })
1000
+ })]
1001
+ }), /*#__PURE__*/_jsxs("button", {
1002
+ type: "button",
1003
+ style: {
1004
+ ...styles.filterOption,
1005
+ ...(isUrlIgnored ? styles.filterOptionActive : null)
1006
+ },
1007
+ onClick: () => urlPath && onTogglePattern(urlPath),
1008
+ children: [/*#__PURE__*/_jsxs("span", {
1009
+ style: styles.filterOptionLeft,
1010
+ children: [/*#__PURE__*/_jsx(Link, {
1011
+ size: 16,
1012
+ color: isUrlIgnored ? macOSColors.semantic.warning : macOSColors.text.muted
1013
+ }), /*#__PURE__*/_jsxs("span", {
1014
+ style: styles.filterOptionContent,
1015
+ children: [/*#__PURE__*/_jsx("span", {
1016
+ style: styles.filterOptionLabel,
1017
+ children: "Ignore URL Pattern"
1018
+ }), /*#__PURE__*/_jsx("span", {
1019
+ style: {
1020
+ ...styles.filterOptionValue,
1021
+ ...styles.singleLine
1022
+ },
1023
+ children: urlPath || "N/A"
1024
+ })]
1025
+ })]
1026
+ }), /*#__PURE__*/_jsx("span", {
1027
+ style: {
1028
+ ...styles.filterToggle,
1029
+ ...(isUrlIgnored ? styles.filterToggleActive : null)
1030
+ },
1031
+ children: /*#__PURE__*/_jsx("span", {
1032
+ style: {
1033
+ ...styles.filterToggleText,
1034
+ ...(isUrlIgnored ? styles.filterToggleTextActive : null)
1035
+ },
1036
+ children: isUrlIgnored ? "IGNORED" : "IGNORE"
1037
+ })
1038
+ })]
1039
+ }), /*#__PURE__*/_jsx("div", {
1040
+ style: styles.filterInfoBox,
1041
+ children: /*#__PURE__*/_jsx("span", {
1042
+ style: styles.filterInfoText,
1043
+ children: "Ignored requests will be hidden from the network list. You can manage filters in the Filters tab."
1044
+ })
1045
+ })]
1046
+ });
1047
+ })()
1048
+ })
1049
+ })]
1050
+ });
1051
+ });
1052
+
1053
+ // Values mirror the native StyleSheet block 1:1.
1054
+ const styles = {
1055
+ // Native: ScrollView flex 1 on background.base — the detail body OWNS its
1056
+ // scrolling so the stepper footer stays pinned under it.
1057
+ container: {
1058
+ flex: 1,
1059
+ minHeight: 0,
1060
+ overflowY: "auto",
1061
+ backgroundColor: macOSColors.background.base
1062
+ },
1063
+ requestDetailsSection: {
1064
+ margin: "12px 12px 0",
1065
+ backgroundColor: macOSColors.background.card,
1066
+ borderRadius: 8,
1067
+ border: `1px solid ${macOSColors.border.default}`,
1068
+ padding: 12
1069
+ },
1070
+ httpHeader: {
1071
+ display: "flex",
1072
+ flexDirection: "row",
1073
+ alignItems: "center",
1074
+ gap: 8,
1075
+ marginBottom: 12,
1076
+ flexWrap: "nowrap"
1077
+ },
1078
+ httpMethodBadge: {
1079
+ display: "inline-flex",
1080
+ backgroundColor: macOSColors.semantic.infoBackground,
1081
+ padding: "4px 8px",
1082
+ borderRadius: 4
1083
+ },
1084
+ httpMethod: {
1085
+ color: macOSColors.semantic.info,
1086
+ fontSize: 11,
1087
+ fontWeight: 700,
1088
+ letterSpacing: 0.5
1089
+ },
1090
+ httpStatusBadge: {
1091
+ display: "inline-flex",
1092
+ padding: "4px 8px",
1093
+ borderRadius: 4,
1094
+ whiteSpace: "nowrap"
1095
+ },
1096
+ httpStatusText: {
1097
+ fontSize: 11,
1098
+ fontWeight: 600
1099
+ },
1100
+ pendingBadge: {
1101
+ display: "inline-flex",
1102
+ flexDirection: "row",
1103
+ alignItems: "center",
1104
+ gap: 4,
1105
+ backgroundColor: macOSColors.semantic.warningBackground,
1106
+ padding: "4px 8px",
1107
+ borderRadius: 4
1108
+ },
1109
+ pendingBadgeText: {
1110
+ color: macOSColors.semantic.warning,
1111
+ fontSize: 11,
1112
+ fontWeight: 600
1113
+ },
1114
+ httpDuration: {
1115
+ display: "inline-flex",
1116
+ flexDirection: "row",
1117
+ alignItems: "center",
1118
+ gap: 4
1119
+ },
1120
+ httpDurationText: {
1121
+ color: macOSColors.text.secondary,
1122
+ fontSize: 11
1123
+ },
1124
+ overrideBadge: {
1125
+ display: "inline-flex",
1126
+ flexDirection: "row",
1127
+ alignItems: "center",
1128
+ gap: 4,
1129
+ padding: "3px 6px",
1130
+ borderRadius: 4,
1131
+ backgroundColor: macOSColors.semantic.warningBackground
1132
+ },
1133
+ overrideBadgeText: {
1134
+ color: macOSColors.semantic.warning,
1135
+ fontSize: 10,
1136
+ fontWeight: 700
1137
+ },
1138
+ copyFullButton: {
1139
+ marginLeft: "auto",
1140
+ flexShrink: 0,
1141
+ padding: 4
1142
+ },
1143
+ // URL breakdown styles
1144
+ urlBreakdown: {
1145
+ backgroundColor: macOSColors.background.input,
1146
+ borderRadius: 4,
1147
+ padding: 8
1148
+ },
1149
+ urlRow: {
1150
+ display: "flex",
1151
+ flexDirection: "row",
1152
+ alignItems: "center",
1153
+ gap: 6,
1154
+ marginBottom: 4
1155
+ },
1156
+ urlDomain: {
1157
+ color: macOSColors.text.primary,
1158
+ fontSize: 12,
1159
+ fontWeight: 600,
1160
+ flex: 1,
1161
+ minWidth: 0,
1162
+ overflowWrap: "anywhere"
1163
+ },
1164
+ urlProtocol: {
1165
+ color: macOSColors.text.muted,
1166
+ fontSize: 10
1167
+ },
1168
+ copyButton: {
1169
+ padding: 4
1170
+ },
1171
+ urlPathRow: {
1172
+ paddingLeft: 18
1173
+ },
1174
+ urlPath: {
1175
+ color: macOSColors.text.secondary,
1176
+ fontSize: 11,
1177
+ fontFamily: "ui-monospace, Menlo, monospace",
1178
+ // Native numberOfLines={2}
1179
+ display: "-webkit-box",
1180
+ WebkitLineClamp: 2,
1181
+ WebkitBoxOrient: "vertical",
1182
+ overflow: "hidden",
1183
+ overflowWrap: "anywhere"
1184
+ },
1185
+ urlQueryString: {
1186
+ color: macOSColors.semantic.info,
1187
+ fontSize: 11,
1188
+ fontFamily: "ui-monospace, Menlo, monospace"
1189
+ },
1190
+ // Query Parameters - Collapsible section
1191
+ queryParamsCollapsible: {
1192
+ marginTop: 8
1193
+ },
1194
+ queryParamsToggle: {
1195
+ display: "flex",
1196
+ width: "100%",
1197
+ boxSizing: "border-box",
1198
+ flexDirection: "row",
1199
+ alignItems: "center",
1200
+ justifyContent: "space-between",
1201
+ gap: 8,
1202
+ padding: "10px 12px",
1203
+ backgroundColor: macOSColors.background.hover,
1204
+ borderRadius: 8,
1205
+ border: `1px solid ${macOSColors.border.default}`,
1206
+ marginBottom: 8,
1207
+ cursor: "pointer",
1208
+ margin: "0 0 8px",
1209
+ font: "inherit"
1210
+ },
1211
+ queryParamsToggleText: {
1212
+ flexGrow: 0,
1213
+ color: macOSColors.text.primary,
1214
+ fontSize: 12,
1215
+ fontWeight: 600,
1216
+ letterSpacing: 0.3,
1217
+ whiteSpace: "nowrap"
1218
+ },
1219
+ // Timing section - always visible
1220
+ timingSection: {
1221
+ margin: "8px 12px 0",
1222
+ backgroundColor: macOSColors.background.card,
1223
+ borderRadius: 8,
1224
+ border: `1px solid ${macOSColors.border.default}`,
1225
+ padding: 12
1226
+ },
1227
+ timingRow: {
1228
+ display: "flex",
1229
+ flexDirection: "row",
1230
+ alignItems: "center",
1231
+ gap: 6,
1232
+ // Wraps rather than squashing when the card is narrow — the sizes drop to
1233
+ // their own line there (native).
1234
+ flexWrap: "wrap"
1235
+ },
1236
+ timingLabel: {
1237
+ color: macOSColors.text.secondary,
1238
+ fontSize: 11
1239
+ },
1240
+ timingValue: {
1241
+ color: macOSColors.text.primary,
1242
+ fontSize: 11,
1243
+ fontWeight: 600
1244
+ },
1245
+ timingExact: {
1246
+ color: macOSColors.text.muted,
1247
+ fontSize: 10,
1248
+ marginLeft: 4
1249
+ },
1250
+ sizeRow: {
1251
+ display: "inline-flex",
1252
+ flexDirection: "row",
1253
+ alignItems: "center",
1254
+ gap: 16,
1255
+ // Pushed to the far end of the Started row (native).
1256
+ marginLeft: "auto"
1257
+ },
1258
+ sizeItem: {
1259
+ display: "inline-flex",
1260
+ flexDirection: "row",
1261
+ alignItems: "center",
1262
+ gap: 4
1263
+ },
1264
+ sizeLabel: {
1265
+ color: macOSColors.text.muted,
1266
+ fontSize: 10
1267
+ },
1268
+ sizeValue: {
1269
+ color: macOSColors.semantic.info,
1270
+ fontSize: 10,
1271
+ fontFamily: "ui-monospace, Menlo, monospace",
1272
+ fontWeight: 600
1273
+ },
1274
+ /* ---- Timing phases ---- */
1275
+ phaseDivider: {
1276
+ height: 1,
1277
+ backgroundColor: macOSColors.border.default,
1278
+ marginTop: 12,
1279
+ marginBottom: 12
1280
+ },
1281
+ phaseList: {
1282
+ display: "flex",
1283
+ flexDirection: "column",
1284
+ gap: 8
1285
+ },
1286
+ phaseRow: {
1287
+ display: "flex",
1288
+ flexDirection: "row",
1289
+ alignItems: "center",
1290
+ gap: 14
1291
+ },
1292
+ phaseLabelGroup: {
1293
+ width: 150,
1294
+ flexShrink: 0
1295
+ },
1296
+ phaseLabel: {
1297
+ fontSize: 12,
1298
+ color: macOSColors.text.secondary
1299
+ },
1300
+ phaseHint: {
1301
+ fontSize: 10,
1302
+ color: macOSColors.text.muted
1303
+ },
1304
+ phaseTrack: {
1305
+ flex: 1,
1306
+ minWidth: 0,
1307
+ height: 7,
1308
+ borderRadius: 4,
1309
+ backgroundColor: macOSColors.background.hover,
1310
+ display: "flex",
1311
+ justifyContent: "center",
1312
+ flexDirection: "column",
1313
+ overflow: "hidden"
1314
+ },
1315
+ phaseBar: {
1316
+ height: 7,
1317
+ borderRadius: 4
1318
+ },
1319
+ phaseValue: {
1320
+ width: 72,
1321
+ flexShrink: 0,
1322
+ textAlign: "right",
1323
+ fontSize: 12,
1324
+ fontFamily: "ui-monospace, Menlo, monospace",
1325
+ color: macOSColors.text.primary
1326
+ },
1327
+ // Collapsible section styles
1328
+ collapsibleSection: {
1329
+ margin: "8px 12px 0",
1330
+ backgroundColor: macOSColors.background.card,
1331
+ borderRadius: 8,
1332
+ border: `1px solid ${macOSColors.border.default}`,
1333
+ overflow: "hidden"
1334
+ },
1335
+ collapsibleHeader: {
1336
+ display: "flex",
1337
+ width: "100%",
1338
+ boxSizing: "border-box",
1339
+ flexDirection: "row",
1340
+ alignItems: "center",
1341
+ justifyContent: "space-between",
1342
+ padding: 12,
1343
+ // Near-white strip, not the selection grey (native rationale).
1344
+ backgroundColor: macOSColors.background.hover,
1345
+ border: "none",
1346
+ cursor: "pointer",
1347
+ margin: 0,
1348
+ font: "inherit",
1349
+ textAlign: "left"
1350
+ },
1351
+ collapsibleTitle: {
1352
+ display: "inline-flex",
1353
+ flexDirection: "row",
1354
+ alignItems: "center",
1355
+ gap: 8
1356
+ },
1357
+ collapsibleTitleText: {
1358
+ color: macOSColors.text.primary,
1359
+ fontSize: 13,
1360
+ fontWeight: 600
1361
+ },
1362
+ /** The "how much is in here" number beside a section title. */
1363
+ collapsibleCount: {
1364
+ color: macOSColors.text.muted,
1365
+ fontSize: 11.5,
1366
+ fontFamily: "ui-monospace, Menlo, monospace"
1367
+ },
1368
+ collapsibleContent: {
1369
+ padding: 12
1370
+ },
1371
+ // Data viewer container — pulls the tree flush against the section edges.
1372
+ dataViewerContainer: {
1373
+ margin: -12
1374
+ },
1375
+ /** Placeholder for a not-yet-mounted body tree. See BodyDeferred. */
1376
+ bodyDeferred: {
1377
+ height: 120
1378
+ },
1379
+ // Error box
1380
+ errorBox: {
1381
+ display: "flex",
1382
+ flexDirection: "row",
1383
+ alignItems: "center",
1384
+ gap: 6,
1385
+ backgroundColor: macOSColors.semantic.errorBackground,
1386
+ padding: 8,
1387
+ borderRadius: 4,
1388
+ marginTop: 8
1389
+ },
1390
+ errorText: {
1391
+ color: macOSColors.semantic.error,
1392
+ fontSize: 11,
1393
+ flex: 1,
1394
+ minWidth: 0,
1395
+ overflowWrap: "anywhere"
1396
+ },
1397
+ // Empty state
1398
+ emptyText: {
1399
+ color: macOSColors.text.muted,
1400
+ fontSize: 12,
1401
+ fontStyle: "italic",
1402
+ textAlign: "center"
1403
+ },
1404
+ // Filter options styles
1405
+ filterOptionsContainer: {
1406
+ display: "flex",
1407
+ flexDirection: "column",
1408
+ gap: 12
1409
+ },
1410
+ filterOption: {
1411
+ display: "flex",
1412
+ width: "100%",
1413
+ boxSizing: "border-box",
1414
+ flexDirection: "row",
1415
+ alignItems: "center",
1416
+ justifyContent: "space-between",
1417
+ backgroundColor: macOSColors.background.hover,
1418
+ borderRadius: 8,
1419
+ padding: 12,
1420
+ border: `1px solid ${macOSColors.border.default}`,
1421
+ cursor: "pointer",
1422
+ margin: 0,
1423
+ font: "inherit",
1424
+ textAlign: "left"
1425
+ },
1426
+ filterOptionActive: {
1427
+ backgroundColor: macOSColors.semantic.warningBackground,
1428
+ border: `1px solid ${macOSColors.semantic.warning}33`
1429
+ },
1430
+ filterOptionLeft: {
1431
+ display: "inline-flex",
1432
+ flexDirection: "row",
1433
+ alignItems: "center",
1434
+ gap: 12,
1435
+ flex: 1,
1436
+ minWidth: 0
1437
+ },
1438
+ filterOptionContent: {
1439
+ display: "inline-flex",
1440
+ flexDirection: "column",
1441
+ flex: 1,
1442
+ minWidth: 0
1443
+ },
1444
+ filterOptionLabel: {
1445
+ color: macOSColors.text.secondary,
1446
+ fontSize: 11,
1447
+ marginBottom: 2,
1448
+ textTransform: "uppercase",
1449
+ letterSpacing: 0.5
1450
+ },
1451
+ filterOptionValue: {
1452
+ color: macOSColors.text.primary,
1453
+ fontSize: 13,
1454
+ fontFamily: "ui-monospace, Menlo, monospace",
1455
+ overflowWrap: "anywhere"
1456
+ },
1457
+ /** Native numberOfLines={1}. */
1458
+ singleLine: {
1459
+ whiteSpace: "nowrap",
1460
+ overflow: "hidden",
1461
+ textOverflow: "ellipsis",
1462
+ display: "block"
1463
+ },
1464
+ filterToggle: {
1465
+ display: "inline-flex",
1466
+ padding: "6px 10px",
1467
+ borderRadius: 4,
1468
+ backgroundColor: macOSColors.background.input,
1469
+ border: `1px solid ${macOSColors.border.default}`,
1470
+ flexShrink: 0
1471
+ },
1472
+ filterToggleActive: {
1473
+ backgroundColor: `${macOSColors.semantic.warning}26`,
1474
+ border: `1px solid ${macOSColors.semantic.warning}4D`
1475
+ },
1476
+ filterToggleText: {
1477
+ fontSize: 10,
1478
+ fontWeight: 600,
1479
+ color: macOSColors.text.muted,
1480
+ letterSpacing: 0.5
1481
+ },
1482
+ filterToggleTextActive: {
1483
+ color: macOSColors.semantic.warning
1484
+ },
1485
+ filterInfoBox: {
1486
+ backgroundColor: macOSColors.semantic.infoBackground,
1487
+ borderRadius: 6,
1488
+ padding: 10,
1489
+ border: `1px solid ${macOSColors.semantic.info}33`
1490
+ },
1491
+ filterInfoText: {
1492
+ color: macOSColors.text.secondary,
1493
+ fontSize: 11,
1494
+ lineHeight: "16px"
1495
+ }
1496
+ };