@buoy-gg/network 7.0.12 → 7.0.14

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 (136) hide show
  1. package/lib/commonjs/index.js +52 -0
  2. package/lib/commonjs/network/components/NetworkDetailStepper.js +26 -14
  3. package/lib/commonjs/network/components/NetworkEventDetailView.js +86 -14
  4. package/lib/commonjs/network/components/NetworkEventItemCompact.js +4 -0
  5. package/lib/commonjs/network/components/NetworkHeaderLive.js +32 -7
  6. package/lib/commonjs/network/components/NetworkHeaderMenu.js +183 -0
  7. package/lib/commonjs/network/components/NetworkListScreen.js +134 -8
  8. package/lib/commonjs/network/components/NetworkModal.js +198 -51
  9. package/lib/commonjs/network/components/NetworkNumberPrompt.js +178 -0
  10. package/lib/commonjs/network/components/NetworkOverrideEditor.js +797 -0
  11. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +102 -0
  12. package/lib/commonjs/network/components/NetworkOverridesButton.js +88 -0
  13. package/lib/commonjs/network/components/NetworkOverridesScreen.js +489 -0
  14. package/lib/commonjs/network/components/response-editor/Explorer.js +973 -0
  15. package/lib/commonjs/network/components/response-editor/deleteNestedDataByPath.js +56 -0
  16. package/lib/commonjs/network/components/response-editor/editorContext.js +33 -0
  17. package/lib/commonjs/network/components/response-editor/updateNestedDataByPath.js +61 -0
  18. package/lib/commonjs/network/hooks/useNetworkSelection.js +53 -0
  19. package/lib/commonjs/network/hooks/useOverrideRules.js +36 -0
  20. package/lib/commonjs/network/overrides/applyToFetch.js +80 -0
  21. package/lib/commonjs/network/overrides/applyToXhr.js +190 -0
  22. package/lib/commonjs/network/overrides/engine.js +70 -0
  23. package/lib/commonjs/network/overrides/matchRule.js +81 -0
  24. package/lib/commonjs/network/overrides/overrideRulesStore.js +550 -0
  25. package/lib/commonjs/network/overrides/presets.js +70 -0
  26. package/lib/commonjs/network/overrides/resolveOverride.js +158 -0
  27. package/lib/commonjs/network/overrides/types.js +85 -0
  28. package/lib/commonjs/network/sync/networkSyncAdapter.js +230 -1
  29. package/lib/commonjs/network/types/index.js +5 -1
  30. package/lib/commonjs/network/utils/networkEventStore.js +52 -6
  31. package/lib/commonjs/network/utils/networkListener.js +90 -10
  32. package/lib/commonjs/network/utils/networkSelectionStore.js +193 -0
  33. package/lib/commonjs/preset.js +11 -0
  34. package/lib/module/index.js +19 -0
  35. package/lib/module/network/components/NetworkDetailStepper.js +27 -15
  36. package/lib/module/network/components/NetworkEventDetailView.js +87 -15
  37. package/lib/module/network/components/NetworkEventItemCompact.js +5 -1
  38. package/lib/module/network/components/NetworkHeaderLive.js +32 -7
  39. package/lib/module/network/components/NetworkHeaderMenu.js +177 -0
  40. package/lib/module/network/components/NetworkListScreen.js +135 -9
  41. package/lib/module/network/components/NetworkModal.js +200 -53
  42. package/lib/module/network/components/NetworkNumberPrompt.js +174 -0
  43. package/lib/module/network/components/NetworkOverrideEditor.js +794 -0
  44. package/lib/module/network/components/NetworkOverrideHeaderButton.js +99 -0
  45. package/lib/module/network/components/NetworkOverridesButton.js +85 -0
  46. package/lib/module/network/components/NetworkOverridesScreen.js +485 -0
  47. package/lib/module/network/components/response-editor/Explorer.js +972 -0
  48. package/lib/module/network/components/response-editor/deleteNestedDataByPath.js +51 -0
  49. package/lib/module/network/components/response-editor/editorContext.js +29 -0
  50. package/lib/module/network/components/response-editor/updateNestedDataByPath.js +56 -0
  51. package/lib/module/network/hooks/useNetworkSelection.js +46 -0
  52. package/lib/module/network/hooks/useOverrideRules.js +32 -0
  53. package/lib/module/network/overrides/applyToFetch.js +72 -0
  54. package/lib/module/network/overrides/applyToXhr.js +185 -0
  55. package/lib/module/network/overrides/engine.js +64 -0
  56. package/lib/module/network/overrides/matchRule.js +75 -0
  57. package/lib/module/network/overrides/overrideRulesStore.js +547 -0
  58. package/lib/module/network/overrides/presets.js +65 -0
  59. package/lib/module/network/overrides/resolveOverride.js +152 -0
  60. package/lib/module/network/overrides/types.js +80 -0
  61. package/lib/module/network/sync/networkSyncAdapter.js +230 -1
  62. package/lib/module/network/types/index.js +3 -1
  63. package/lib/module/network/utils/networkEventStore.js +52 -6
  64. package/lib/module/network/utils/networkListener.js +91 -10
  65. package/lib/module/network/utils/networkSelectionStore.js +188 -0
  66. package/lib/module/preset.js +13 -1
  67. package/lib/typescript/index.d.ts +9 -0
  68. package/lib/typescript/index.d.ts.map +1 -1
  69. package/lib/typescript/network/components/NetworkDetailStepper.d.ts +8 -5
  70. package/lib/typescript/network/components/NetworkDetailStepper.d.ts.map +1 -1
  71. package/lib/typescript/network/components/NetworkEventDetailView.d.ts +36 -1
  72. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -1
  73. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +6 -0
  74. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
  75. package/lib/typescript/network/components/NetworkHeaderLive.d.ts +13 -3
  76. package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -1
  77. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts +46 -0
  78. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +1 -0
  79. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
  80. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  81. package/lib/typescript/network/components/NetworkNumberPrompt.d.ts +33 -0
  82. package/lib/typescript/network/components/NetworkNumberPrompt.d.ts.map +1 -0
  83. package/lib/typescript/network/components/NetworkOverrideEditor.d.ts +17 -0
  84. package/lib/typescript/network/components/NetworkOverrideEditor.d.ts.map +1 -0
  85. package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts +27 -0
  86. package/lib/typescript/network/components/NetworkOverrideHeaderButton.d.ts.map +1 -0
  87. package/lib/typescript/network/components/NetworkOverridesButton.d.ts +17 -0
  88. package/lib/typescript/network/components/NetworkOverridesButton.d.ts.map +1 -0
  89. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts +29 -0
  90. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -0
  91. package/lib/typescript/network/components/response-editor/Explorer.d.ts +17 -0
  92. package/lib/typescript/network/components/response-editor/Explorer.d.ts.map +1 -0
  93. package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts +9 -0
  94. package/lib/typescript/network/components/response-editor/deleteNestedDataByPath.d.ts.map +1 -0
  95. package/lib/typescript/network/components/response-editor/editorContext.d.ts +29 -0
  96. package/lib/typescript/network/components/response-editor/editorContext.d.ts.map +1 -0
  97. package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts +10 -0
  98. package/lib/typescript/network/components/response-editor/updateNestedDataByPath.d.ts.map +1 -0
  99. package/lib/typescript/network/hooks/useNetworkSelection.d.ts +26 -0
  100. package/lib/typescript/network/hooks/useNetworkSelection.d.ts.map +1 -0
  101. package/lib/typescript/network/hooks/useOverrideRules.d.ts +29 -0
  102. package/lib/typescript/network/hooks/useOverrideRules.d.ts.map +1 -0
  103. package/lib/typescript/network/overrides/__tests__/applyToFetch.test.d.ts +16 -0
  104. package/lib/typescript/network/overrides/__tests__/applyToFetch.test.d.ts.map +1 -0
  105. package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts +19 -0
  106. package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts.map +1 -0
  107. package/lib/typescript/network/overrides/__tests__/resolveOverride.test.d.ts +22 -0
  108. package/lib/typescript/network/overrides/__tests__/resolveOverride.test.d.ts.map +1 -0
  109. package/lib/typescript/network/overrides/applyToFetch.d.ts +50 -0
  110. package/lib/typescript/network/overrides/applyToFetch.d.ts.map +1 -0
  111. package/lib/typescript/network/overrides/applyToXhr.d.ts +43 -0
  112. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -0
  113. package/lib/typescript/network/overrides/engine.d.ts +47 -0
  114. package/lib/typescript/network/overrides/engine.d.ts.map +1 -0
  115. package/lib/typescript/network/overrides/matchRule.d.ts +22 -0
  116. package/lib/typescript/network/overrides/matchRule.d.ts.map +1 -0
  117. package/lib/typescript/network/overrides/overrideRulesStore.d.ts +205 -0
  118. package/lib/typescript/network/overrides/overrideRulesStore.d.ts.map +1 -0
  119. package/lib/typescript/network/overrides/presets.d.ts +38 -0
  120. package/lib/typescript/network/overrides/presets.d.ts.map +1 -0
  121. package/lib/typescript/network/overrides/resolveOverride.d.ts +46 -0
  122. package/lib/typescript/network/overrides/resolveOverride.d.ts.map +1 -0
  123. package/lib/typescript/network/overrides/types.d.ts +169 -0
  124. package/lib/typescript/network/overrides/types.d.ts.map +1 -0
  125. package/lib/typescript/network/sync/networkSyncAdapter.d.ts +73 -0
  126. package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
  127. package/lib/typescript/network/types/index.d.ts +10 -0
  128. package/lib/typescript/network/types/index.d.ts.map +1 -1
  129. package/lib/typescript/network/utils/networkEventStore.d.ts +14 -0
  130. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  131. package/lib/typescript/network/utils/networkListener.d.ts +3 -0
  132. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  133. package/lib/typescript/network/utils/networkSelectionStore.d.ts +117 -0
  134. package/lib/typescript/network/utils/networkSelectionStore.d.ts.map +1 -0
  135. package/lib/typescript/preset.d.ts.map +1 -1
  136. package/package.json +2 -2
@@ -3,12 +3,30 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "DEFAULT_SETTLE_MS", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _networkSelectionStore.DEFAULT_SETTLE_MS;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "FREE_TIER_OVERRIDE_LIMIT", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _types.FREE_TIER_RULE_LIMIT;
16
+ }
17
+ });
6
18
  Object.defineProperty(exports, "FREE_TIER_SAVED_LIMIT", {
7
19
  enumerable: true,
8
20
  get: function () {
9
21
  return _networkSavedStore.FREE_TIER_SAVED_LIMIT;
10
22
  }
11
23
  });
24
+ Object.defineProperty(exports, "MAX_OVERRIDE_RULES", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _types.MAX_RULES;
28
+ }
29
+ });
12
30
  Object.defineProperty(exports, "MAX_PINNED", {
13
31
  enumerable: true,
14
32
  get: function () {
@@ -69,6 +87,18 @@ Object.defineProperty(exports, "NetworkToolView", {
69
87
  return _NetworkModal.NetworkToolView;
70
88
  }
71
89
  });
90
+ Object.defineProperty(exports, "OVERRIDE_STATUS_MAX", {
91
+ enumerable: true,
92
+ get: function () {
93
+ return _types.STATUS_MAX;
94
+ }
95
+ });
96
+ Object.defineProperty(exports, "OVERRIDE_STATUS_MIN", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _types.STATUS_MIN;
100
+ }
101
+ });
72
102
  Object.defineProperty(exports, "TickProvider", {
73
103
  enumerable: true,
74
104
  get: function () {
@@ -87,6 +117,12 @@ Object.defineProperty(exports, "createNetworkTool", {
87
117
  return _preset.createNetworkTool;
88
118
  }
89
119
  });
120
+ Object.defineProperty(exports, "draftFromEvent", {
121
+ enumerable: true,
122
+ get: function () {
123
+ return _presets.draftFromEvent;
124
+ }
125
+ });
90
126
  Object.defineProperty(exports, "flagsForEventId", {
91
127
  enumerable: true,
92
128
  get: function () {
@@ -147,6 +183,12 @@ Object.defineProperty(exports, "networkSavedStore", {
147
183
  return _networkSavedStore.networkSavedStore;
148
184
  }
149
185
  });
186
+ Object.defineProperty(exports, "networkSelectionStore", {
187
+ enumerable: true,
188
+ get: function () {
189
+ return _networkSelectionStore.networkSelectionStore;
190
+ }
191
+ });
150
192
  Object.defineProperty(exports, "networkSyncAdapter", {
151
193
  enumerable: true,
152
194
  get: function () {
@@ -159,6 +201,12 @@ Object.defineProperty(exports, "networkToolPreset", {
159
201
  return _preset.networkToolPreset;
160
202
  }
161
203
  });
204
+ Object.defineProperty(exports, "overrideRulesStore", {
205
+ enumerable: true,
206
+ get: function () {
207
+ return _overrideRulesStore.overrideRulesStore;
208
+ }
209
+ });
162
210
  Object.defineProperty(exports, "removeAllNetworkListeners", {
163
211
  enumerable: true,
164
212
  get: function () {
@@ -231,8 +279,12 @@ var _useLiveNetworkEvent = require("./network/hooks/useLiveNetworkEvent");
231
279
  var _useNetworkBadgeCounts = require("./network/hooks/useNetworkBadgeCounts");
232
280
  var _useNetworkSaved = require("./network/hooks/useNetworkSaved");
233
281
  var _withPinnedFirst = require("./network/utils/withPinnedFirst");
282
+ var _networkSelectionStore = require("./network/utils/networkSelectionStore");
234
283
  var _useTickEveryMinute = require("./network/hooks/useTickEveryMinute");
235
284
  var _formatting = require("./network/utils/formatting");
285
+ var _overrideRulesStore = require("./network/overrides/overrideRulesStore");
286
+ var _presets = require("./network/overrides/presets");
287
+ var _types = require("./network/overrides/types");
236
288
  var _sharedUi = require("@buoy-gg/shared-ui");
237
289
  var _networkSyncAdapter = require("./network/sync/networkSyncAdapter");
238
290
  var _networkBodyResolver = require("./network/sync/networkBodyResolver");
@@ -8,6 +8,8 @@ var _react = require("react");
8
8
  var _sharedUi = require("@buoy-gg/shared-ui");
9
9
  var _useNetworkEventList = require("../hooks/useNetworkEventList");
10
10
  var _useNetworkSaved = require("../hooks/useNetworkSaved");
11
+ var _useNetworkSelection = require("../hooks/useNetworkSelection");
12
+ var _networkSelectionStore = require("../utils/networkSelectionStore");
11
13
  var _withPinnedFirst = require("../utils/withPinnedFirst");
12
14
  var _selectSavedEvents = require("../utils/selectSavedEvents");
13
15
  var _jsxRuntime = require("react/jsx-runtime");
@@ -18,8 +20,8 @@ var _jsxRuntime = require("react/jsx-runtime");
18
20
  * It is its OWN leaf (rule3) because it is the only part of the detail screen
19
21
  * that needs the event LIST. Subscribing here keeps the request stream out of
20
22
  * the detail body's render path, which is the expensive subtree (headers,
21
- * bodies, JSON viewers, diff views): a request arriving re-renders this
22
- * ~30-node footer and nothing else.
23
+ * bodies, JSON viewers): a request arriving re-renders this ~30-node footer
24
+ * and nothing else.
23
25
  *
24
26
  * The order it steps through is EXACTLY what the list screen renders —
25
27
  * `[...pinned, ...rest]` off the same `useNetworkEventList` + `splitPinned`
@@ -29,17 +31,24 @@ var _jsxRuntime = require("react/jsx-runtime");
29
31
  * "request N of <what the list is showing>", never "N of everything ever
30
32
  * captured". Detail opened from the Saved screen steps through the SAVED
31
33
  * list instead (with its own search applied), for the same reason.
34
+ *
35
+ * This component OWNS the navigable order but not the cursor: it publishes
36
+ * the ordered ids into `networkSelectionStore` and reads the cursor back.
37
+ * That split is what makes stepping O(1) (the store keeps the index, so
38
+ * there is no `findIndex` per render) and what lets the benchmark harness
39
+ * drive `step(+1)` directly without going through the UI.
32
40
  */
33
41
 
42
+ /** Stable identities — the store is a singleton, so these never need to change. */
43
+ const stepPrevious = () => _networkSelectionStore.networkSelectionStore.step(-1);
44
+ const stepNext = () => _networkSelectionStore.networkSelectionStore.step(1);
34
45
  const NetworkDetailStepper = exports.NetworkDetailStepper = /*#__PURE__*/(0, _react.memo)(function NetworkDetailStepper({
35
- eventId,
36
46
  paused,
37
47
  filter,
38
48
  ignoredPatterns,
39
49
  isPro,
40
50
  fromSaved,
41
51
  savedSearch,
42
- onSelect,
43
52
  applySafeAreaInset
44
53
  }) {
45
54
  const {
@@ -67,7 +76,16 @@ const NetworkDetailStepper = exports.NetworkDetailStepper = /*#__PURE__*/(0, _re
67
76
  } = (0, _withPinnedFirst.splitPinned)(visibleEvents, pinnedEvents, pinnedLiveIds);
68
77
  return pinned.length === 0 ? rest : [...pinned, ...rest];
69
78
  }, [fromSaved, savedRecords, savedSearch, isPro, visibleEvents, pinnedEvents, pinnedLiveIds]);
70
- const index = ordered.findIndex(event => event.id === eventId);
79
+
80
+ // Publish the navigable order. `setIds` re-anchors the cursor BY ID and
81
+ // no-ops when the ids are unchanged, so this is safe to run every render.
82
+ (0, _react.useEffect)(() => {
83
+ _networkSelectionStore.networkSelectionStore.setIds((0, _networkSelectionStore.toSelectionIds)(ordered));
84
+ }, [ordered]);
85
+ const {
86
+ index,
87
+ total
88
+ } = (0, _useNetworkSelection.useNetworkCursor)();
71
89
 
72
90
  // Not in the list at all: the filter changed under it while it was open, it
73
91
  // aged past the 500-request cap, or it is a saved snapshot with no live twin.
@@ -79,16 +97,10 @@ const NetworkDetailStepper = exports.NetworkDetailStepper = /*#__PURE__*/(0, _re
79
97
  // the same direction as scrolling up, which is what the arrow means here.
80
98
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EventStepperFooter, {
81
99
  currentIndex: index,
82
- totalItems: ordered.length,
100
+ totalItems: total,
83
101
  itemLabel: "Request",
84
102
  applySafeAreaInset: applySafeAreaInset,
85
- onPrevious: () => {
86
- const target = ordered[index - 1];
87
- if (target) onSelect(target.id);
88
- },
89
- onNext: () => {
90
- const target = ordered[index + 1];
91
- if (target) onSelect(target.id);
92
- }
103
+ onPrevious: stepPrevious,
104
+ onNext: stepNext
93
105
  });
94
106
  });
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.NetworkEventDetailView = NetworkEventDetailView;
6
+ exports.NetworkEventDetailView = void 0;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _sharedUi = require("@buoy-gg/shared-ui");
@@ -17,6 +17,10 @@ var _jsxRuntime = require("react/jsx-runtime");
17
17
  * body glyphs). The Lock/Unlock pair keeps its colour because
18
18
  * secure-vs-insecure is a genuine signal, as does the status pill.
19
19
  */const NEUTRAL_ICON = _sharedUi.macOSColors.semantic.success;
20
+
21
+ /** Hoisted so the defaults don't mint a new Set/function per render. */
22
+ const EMPTY_PATTERNS = new Set();
23
+ const noop = () => {};
20
24
  /**
21
25
  * The measurable slice of a request's lifetime, as proportional bars.
22
26
  *
@@ -70,8 +74,11 @@ const TimingPhases = ({
70
74
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
71
75
  style: [styles.phaseBar, {
72
76
  backgroundColor: phase.color,
73
- // A floor so a 1ms phase is still a visible tick.
74
- width: `${Math.max(1, phase.value / span * 100)}%`
77
+ // A floor so a 1ms phase is still a visible tick, and a
78
+ // ceiling because `total` and the phases are measured at
79
+ // different points — ttfb can exceed event.duration, which
80
+ // would push the bar past its track and over the label.
81
+ width: `${Math.min(100, Math.max(1, phase.value / span * 100))}%`
75
82
  }]
76
83
  })
77
84
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
@@ -82,6 +89,17 @@ const TimingPhases = ({
82
89
  });
83
90
  };
84
91
 
92
+ /**
93
+ * Stand-in for a body tree while the prev/next cursor is still moving.
94
+ *
95
+ * Deliberately a fixed-height nothing: it must cost ~0 to render (that is the
96
+ * entire point) and it must not resize the scroll view as the real tree swaps
97
+ * in, or fast stepping would make the page jump under the reader.
98
+ */
99
+ const BodyDeferred = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
100
+ style: styles.bodyDeferred
101
+ });
102
+
85
103
  // Component for collapsible sections matching Sentry style
86
104
  const CollapsibleSection = ({
87
105
  title,
@@ -261,11 +279,30 @@ const UrlBreakdown = ({
261
279
  /**
262
280
  * Detailed inspector rendering request/response metadata for a single network event. Designed to
263
281
  * mimic Sentry’s layout with collapsible sections, diff views, and quick copy affordances.
282
+ *
283
+ * MEMOIZED with profiling evidence, not by reflex. Two render paths hit this
284
+ * subtree with unchanged props:
285
+ *
286
+ * 1. `useLiveNetworkEvent` re-subscribes when the selected ID changes, and
287
+ * `subscribeToEvents` replays immediately — so every step through the
288
+ * prev/next footer committed the SAME event object twice, rendering this
289
+ * whole tree (expanded DataViewer included) a second time for nothing.
290
+ * 2. Any modal-root state change (header search, mode toggle) re-renders the
291
+ * shell, and the detail body has nothing to do with it.
292
+ *
293
+ * Both props besides `event` are stable by construction — `values` is memoized
294
+ * in `useIgnoredPatterns` and `toggle` is module-scope there — so the bail-out
295
+ * actually fires.
296
+ *
297
+ * Measured baseline (js-top, example-dev-build, 5 steps over 109–324KB
298
+ * pokeapi responses): 307ms of React work across 5 steps ≈ 61ms of JS per
299
+ * step. Case 1 alone is half of that, since it re-ran the whole body render.
264
300
  */
265
- function NetworkEventDetailView({
301
+ const NetworkEventDetailView = exports.NetworkEventDetailView = /*#__PURE__*/(0, _react.memo)(function NetworkEventDetailView({
266
302
  event,
267
- ignoredPatterns = new Set(),
268
- onTogglePattern = () => {}
303
+ ignoredPatterns = EMPTY_PATTERNS,
304
+ onTogglePattern = noop,
305
+ showBodies = true
269
306
  }) {
270
307
  const status = event.status ? (0, _formatting.formatHttpStatus)(event.status) : null;
271
308
  const isPending = !event.status && !event.error;
@@ -409,8 +446,24 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
409
446
  style: styles.httpDurationText,
410
447
  children: (0, _formatting.formatDuration)(event.duration)
411
448
  })]
412
- }) : null, isPro ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton, {
413
- value: getFullRequestDetails(),
449
+ }) : null, event.override ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
450
+ style: styles.overrideBadge,
451
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FlaskConical, {
452
+ size: 10,
453
+ color: _sharedUi.macOSColors.semantic.warning
454
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
455
+ style: styles.overrideBadgeText,
456
+ children: event.override.ruleName || "Overridden"
457
+ })]
458
+ }) : null, isPro ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton
459
+ // LAZY, not `getFullRequestDetails()`. CopyButton resolves a
460
+ // function value at press time; calling it here instead
461
+ // pretty-printed the headers AND both bodies on every render —
462
+ // four JSON.stringify passes over the full payload just to have
463
+ // a string nobody asked for yet. Same shape NetworkSavedScreen
464
+ // already uses for its copy button.
465
+ , {
466
+ value: getFullRequestDetails,
414
467
  buttonStyle: styles.copyFullButton
415
468
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
416
469
  style: styles.copyFullButton,
@@ -544,14 +597,14 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
544
597
  defaultOpen: false,
545
598
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
546
599
  style: styles.dataViewerContainer,
547
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
600
+ children: showBodies ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
548
601
  title: "",
549
602
  data: requestData,
550
603
  showTypeFilter: true,
551
604
  rawMode: true,
552
605
  initialExpanded: true,
553
606
  disableCopy: !isPro
554
- })
607
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(BodyDeferred, {})
555
608
  })
556
609
  }) : requestBodyOmitted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
557
610
  title: "Request Body",
@@ -576,14 +629,14 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
576
629
  defaultOpen: true,
577
630
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
578
631
  style: styles.dataViewerContainer,
579
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
632
+ children: showBodies ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
580
633
  title: "",
581
634
  data: responseData,
582
635
  showTypeFilter: true,
583
636
  rawMode: true,
584
637
  initialExpanded: true,
585
638
  disableCopy: !isPro
586
- })
639
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(BodyDeferred, {})
587
640
  })
588
641
  }) : responseBodyOmitted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
589
642
  title: "Response Body",
@@ -688,7 +741,7 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
688
741
  benefits: ["Copy full request details including headers and body", "Export to JSON, Markdown, or plain text", "Share request data with teammates", "Feed data to AI assistants for debugging"]
689
742
  })]
690
743
  });
691
- }
744
+ });
692
745
  const styles = _reactNative.StyleSheet.create({
693
746
  container: {
694
747
  flex: 1,
@@ -755,6 +808,20 @@ const styles = _reactNative.StyleSheet.create({
755
808
  color: _sharedUi.macOSColors.text.secondary,
756
809
  fontSize: 11
757
810
  },
811
+ overrideBadge: {
812
+ flexDirection: "row",
813
+ alignItems: "center",
814
+ gap: 4,
815
+ paddingHorizontal: 6,
816
+ paddingVertical: 3,
817
+ borderRadius: 4,
818
+ backgroundColor: _sharedUi.macOSColors.semantic.warningBackground
819
+ },
820
+ overrideBadgeText: {
821
+ color: _sharedUi.macOSColors.semantic.warning,
822
+ fontSize: 10,
823
+ fontWeight: "700"
824
+ },
758
825
  copyFullButton: {
759
826
  marginLeft: "auto",
760
827
  flexShrink: 0,
@@ -907,7 +974,8 @@ const styles = _reactNative.StyleSheet.create({
907
974
  height: 7,
908
975
  borderRadius: 4,
909
976
  backgroundColor: _sharedUi.macOSColors.background.hover,
910
- justifyContent: "center"
977
+ justifyContent: "center",
978
+ overflow: "hidden"
911
979
  },
912
980
  phaseBar: {
913
981
  height: 7,
@@ -964,6 +1032,10 @@ const styles = _reactNative.StyleSheet.create({
964
1032
  marginHorizontal: -12,
965
1033
  marginBottom: -12
966
1034
  },
1035
+ /** Placeholder for a not-yet-mounted body tree. See BodyDeferred. */
1036
+ bodyDeferred: {
1037
+ height: 120
1038
+ },
967
1039
  // Error box
968
1040
  errorBox: {
969
1041
  flexDirection: "row",
@@ -173,6 +173,7 @@ function NetworkEventItemCompact({
173
173
  onPress,
174
174
  pinned = false,
175
175
  saved = false,
176
+ overridden = false,
176
177
  onLongPress
177
178
  }) {
178
179
  const statusColor = getStatusColor(event.status, event.error);
@@ -211,6 +212,9 @@ function NetworkEventItemCompact({
211
212
  }) : null, saved ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Bookmark, {
212
213
  size: 10,
213
214
  color: _sharedUi.macOSColors.semantic.debug
215
+ }) : null, overridden || event.override ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FlaskConical, {
216
+ size: 10,
217
+ color: _sharedUi.macOSColors.semantic.warning
214
218
  }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusIndicator, {
215
219
  event: event,
216
220
  isPending: isPending,
@@ -13,6 +13,7 @@ var _sharedUi = require("@buoy-gg/shared-ui");
13
13
  var _networkEventStore = require("../utils/networkEventStore");
14
14
  var _networkSavedStore = require("../utils/networkSavedStore");
15
15
  var _useNetworkSaved = require("../hooks/useNetworkSaved");
16
+ var _useNetworkSelection = require("../hooks/useNetworkSelection");
16
17
  var _useNetworkBadgeCounts = require("../hooks/useNetworkBadgeCounts");
17
18
  var _useNetworkHasMatches = require("../hooks/useNetworkHasMatches");
18
19
  var _generateNetworkCopyText = require("../utils/generateNetworkCopyText");
@@ -149,12 +150,16 @@ function NetworkSavedButton({
149
150
  * The event object is resolved at PRESS time rather than held as a prop: the
150
151
  * live store has the fresh copy while the request is in flight, and the saved
151
152
  * store is the only copy once it has been cleared or aged out.
153
+ *
154
+ * The selected id is SUBSCRIBED here rather than passed down, so stepping
155
+ * through requests doesn't have to re-render the modal root just to rebuild
156
+ * the header around a new prop.
152
157
  */
153
158
  function NetworkDetailPinActions({
154
- eventId,
155
159
  isPro,
156
160
  onUpgradePress
157
161
  }) {
162
+ const eventId = (0, _useNetworkSelection.useSelectedNetworkEventId)();
158
163
  const state = (0, _useNetworkSaved.useNetworkSaved)();
159
164
  const {
160
165
  pinned,
@@ -217,7 +222,8 @@ function NetworkHeaderCopyButton({
217
222
  ignoredPatterns,
218
223
  copySettings,
219
224
  paused,
220
- onUpgradePress
225
+ onUpgradePress,
226
+ label
221
227
  }) {
222
228
  const hasEvents = (0, _useNetworkHasMatches.useNetworkHasMatches)({
223
229
  filter,
@@ -225,15 +231,18 @@ function NetworkHeaderCopyButton({
225
231
  paused
226
232
  });
227
233
  if (!isPro) {
228
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
234
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
229
235
  "sentry-label": "ignore copy pro",
230
236
  onPress: onUpgradePress,
231
- style: [styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
237
+ style: [label ? styles.copyMenuRow : styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
232
238
  disabled: !hasEvents,
233
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Copy, {
239
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Copy, {
234
240
  size: 14,
235
241
  color: hasEvents ? _sharedUi.macOSColors.text.secondary : _sharedUi.macOSColors.text.disabled
236
- })
242
+ }), label ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
243
+ style: styles.copyMenuLabel,
244
+ children: label
245
+ }) : null]
237
246
  });
238
247
  }
239
248
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton
@@ -242,7 +251,9 @@ function NetworkHeaderCopyButton({
242
251
  , {
243
252
  value: () => (0, _generateNetworkCopyText.generateNetworkCopyText)((0, _filterNetworkEvents.filterNetworkEvents)((0, _filterNetworkEvents.applyIgnoredPatterns)(_networkEventStore.networkEventStore.getEvents(), ignoredPatterns), filter), copySettings),
244
253
  size: 14,
245
- buttonStyle: styles.headerActionButton,
254
+ label: label,
255
+ buttonStyle: label ? styles.copyMenuRow : styles.headerActionButton,
256
+ labelStyle: styles.copyMenuLabel,
246
257
  colors: {
247
258
  idle: hasEvents ? _sharedUi.macOSColors.text.secondary : _sharedUi.macOSColors.text.disabled,
248
259
  success: _sharedUi.macOSColors.semantic.success,
@@ -252,6 +263,20 @@ function NetworkHeaderCopyButton({
252
263
  });
253
264
  }
254
265
  const styles = _reactNative.StyleSheet.create({
266
+ /** The overflow-menu variant: a full-width row, matching the other items. */
267
+ copyMenuRow: {
268
+ flexDirection: "row",
269
+ alignItems: "center",
270
+ gap: 10,
271
+ paddingHorizontal: 6,
272
+ paddingVertical: 11,
273
+ justifyContent: "flex-start"
274
+ },
275
+ copyMenuLabel: {
276
+ fontSize: 13,
277
+ fontWeight: "600",
278
+ color: _sharedUi.macOSColors.text.primary
279
+ },
255
280
  headerChipRow: {
256
281
  flexDirection: "row",
257
282
  alignItems: "center",
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkHeaderMenu = NetworkHeaderMenu;
7
+ exports.NetworkHeaderMenuButton = NetworkHeaderMenuButton;
8
+ exports.NetworkHeaderMenuItem = NetworkHeaderMenuItem;
9
+ var _reactNative = require("react-native");
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ /**
13
+ * The header's overflow menu.
14
+ *
15
+ * The toolbar had grown to three status badges and six icon buttons, which on a
16
+ * 402pt screen meant the pending badge was being overlapped by the search
17
+ * button. Something had to leave, and the rule for deciding what is:
18
+ *
19
+ * **A control that COMMUNICATES STATE stays visible. A destination or a rare
20
+ * action goes in the menu.**
21
+ *
22
+ * That keeps the power toggle (are we capturing?), the overrides flask (is the
23
+ * app being lied to, and how many rules?), the status badges (what happened)
24
+ * and Clear (the reproduce loop is clear → repro → read, and burying the first
25
+ * step doubles it). Search stays because it's the only way through 500 rows.
26
+ *
27
+ * Filters, Saved and Copy leave. Filters is the interesting one — it does show
28
+ * state, so the ⋯ carries a dot when a filter is applied rather than hiding
29
+ * that fact.
30
+ */
31
+
32
+ function NetworkHeaderMenuButton({
33
+ onPress,
34
+ isOpen,
35
+ hasIndicator
36
+ }) {
37
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
38
+ onPress: onPress,
39
+ "sentry-label": "ignore toggle header menu",
40
+ accessibilityLabel: isOpen ? "Close menu" : "More actions",
41
+ style: [styles.button, isOpen && styles.buttonOpen],
42
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
43
+ style: styles.dots,
44
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
45
+ style: [styles.dot, isOpen && styles.dotOpen]
46
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
47
+ style: [styles.dot, isOpen && styles.dotOpen]
48
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
49
+ style: [styles.dot, isOpen && styles.dotOpen]
50
+ })]
51
+ }), hasIndicator ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
52
+ style: styles.indicator
53
+ }) : null]
54
+ });
55
+ }
56
+ /**
57
+ * The dropdown itself, rendered in the modal BODY rather than the header —
58
+ * the header is a fixed-height row and anything overflowing it is clipped, so
59
+ * a panel anchored to the top-right of the body lands exactly where a dropdown
60
+ * from the ⋯ should appear.
61
+ */
62
+ function NetworkHeaderMenu({
63
+ onClose,
64
+ children
65
+ }) {
66
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
67
+ style: styles.overlay,
68
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
69
+ style: styles.backdrop,
70
+ activeOpacity: 1,
71
+ onPress: onClose,
72
+ "sentry-label": "ignore dismiss header menu"
73
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
74
+ style: styles.panel,
75
+ children: children
76
+ })]
77
+ });
78
+ }
79
+ function NetworkHeaderMenuItem({
80
+ icon,
81
+ label,
82
+ detail,
83
+ onPress
84
+ }) {
85
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
86
+ onPress: onPress,
87
+ "sentry-label": "ignore header menu item",
88
+ style: styles.item,
89
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
90
+ style: styles.itemIcon,
91
+ children: icon
92
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
93
+ style: styles.itemLabel,
94
+ children: label
95
+ }), detail ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
96
+ style: styles.itemDetail,
97
+ children: detail
98
+ }) : null]
99
+ });
100
+ }
101
+ const styles = _reactNative.StyleSheet.create({
102
+ // The same 32×32 box every other header action uses, minus the fill and
103
+ // border. Padding alone gave a 3pt-wide hit area AND a shorter box than its
104
+ // neighbours, which is why the dots sat high in the row; a fixed square
105
+ // centres the glyph on the same baseline as the search/power/clear icons.
106
+ button: {
107
+ width: 32,
108
+ height: 32,
109
+ borderRadius: 8,
110
+ alignItems: "center",
111
+ justifyContent: "center"
112
+ },
113
+ // Only lit while the menu is down — the button is the one thing not covered
114
+ // by the backdrop, so it has to look like the thing that is currently open.
115
+ buttonOpen: {
116
+ backgroundColor: _sharedUi.macOSColors.background.hover
117
+ },
118
+ dots: {
119
+ flexDirection: "column",
120
+ gap: 2.5,
121
+ alignItems: "center"
122
+ },
123
+ dot: {
124
+ width: 3,
125
+ height: 3,
126
+ borderRadius: 2,
127
+ backgroundColor: _sharedUi.macOSColors.text.secondary
128
+ },
129
+ dotOpen: {
130
+ backgroundColor: _sharedUi.macOSColors.text.primary
131
+ },
132
+ indicator: {
133
+ position: "absolute",
134
+ // Sits just up-and-right of the top dot. Anchored to the 32pt box's corner
135
+ // it would float off on its own, since the dot column is only 3pt wide.
136
+ top: 6,
137
+ right: 8,
138
+ width: 6,
139
+ height: 6,
140
+ borderRadius: 3,
141
+ backgroundColor: _sharedUi.macOSColors.semantic.info
142
+ },
143
+ overlay: {
144
+ ..._sharedUi.absoluteFill,
145
+ zIndex: 50
146
+ },
147
+ backdrop: {
148
+ ..._sharedUi.absoluteFill,
149
+ backgroundColor: "rgba(0,0,0,0.5)"
150
+ },
151
+ panel: {
152
+ position: "absolute",
153
+ top: 6,
154
+ right: 8,
155
+ minWidth: 196,
156
+ paddingVertical: 4,
157
+ borderRadius: 8,
158
+ borderWidth: 1,
159
+ borderColor: _sharedUi.macOSColors.border.hover,
160
+ backgroundColor: _sharedUi.macOSColors.background.card
161
+ },
162
+ item: {
163
+ flexDirection: "row",
164
+ alignItems: "center",
165
+ gap: 10,
166
+ paddingHorizontal: 12,
167
+ paddingVertical: 11
168
+ },
169
+ itemIcon: {
170
+ width: 16,
171
+ alignItems: "center"
172
+ },
173
+ itemLabel: {
174
+ flex: 1,
175
+ fontSize: 13,
176
+ fontWeight: "600",
177
+ color: _sharedUi.macOSColors.text.primary
178
+ },
179
+ itemDetail: {
180
+ fontSize: 11,
181
+ color: _sharedUi.macOSColors.text.muted
182
+ }
183
+ });