@buoy-gg/network 7.0.32 → 7.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/lib/commonjs/index.js +6 -0
  2. package/lib/commonjs/network/components/NetworkCopySettingsView.js +10 -121
  3. package/lib/commonjs/network/components/NetworkHeaderLive.js +16 -0
  4. package/lib/commonjs/network/components/NetworkListScreen.js +34 -46
  5. package/lib/commonjs/network/components/NetworkModal.js +24 -53
  6. package/lib/commonjs/network/components/NetworkOverridesScreen.js +11 -2
  7. package/lib/commonjs/network/components/NetworkSavedScreen.js +19 -38
  8. package/lib/commonjs/network/hooks/useNetworkEventList.js +7 -4
  9. package/lib/commonjs/network/sync/networkSyncAdapter.js +8 -0
  10. package/lib/commonjs/preset.js +6 -21
  11. package/lib/module/index.js +1 -1
  12. package/lib/module/network/components/NetworkCopySettingsView.js +13 -124
  13. package/lib/module/network/components/NetworkHeaderLive.js +15 -0
  14. package/lib/module/network/components/NetworkListScreen.js +35 -47
  15. package/lib/module/network/components/NetworkModal.js +26 -55
  16. package/lib/module/network/components/NetworkOverridesScreen.js +12 -3
  17. package/lib/module/network/components/NetworkSavedScreen.js +20 -39
  18. package/lib/module/network/hooks/useNetworkEventList.js +7 -4
  19. package/lib/module/network/sync/networkSyncAdapter.js +8 -0
  20. package/lib/module/preset.js +6 -20
  21. package/lib/typescript/index.d.ts +1 -1
  22. package/lib/typescript/index.d.ts.map +1 -1
  23. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +1 -1
  24. package/lib/typescript/network/components/NetworkHeaderLive.d.ts +11 -0
  25. package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -1
  26. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
  27. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  28. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -1
  29. package/lib/typescript/network/components/NetworkSavedScreen.d.ts.map +1 -1
  30. package/lib/typescript/network/hooks/useNetworkEventList.d.ts +11 -0
  31. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -1
  32. package/lib/typescript/network/sync/networkSyncAdapter.d.ts +4 -0
  33. package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
  34. package/lib/typescript/preset.d.ts +2 -8
  35. package/lib/typescript/preset.d.ts.map +1 -1
  36. package/package.json +2 -2
@@ -63,6 +63,12 @@ Object.defineProperty(exports, "NetworkEventItemCompact", {
63
63
  return _NetworkEventItemCompact.NetworkEventItemCompact;
64
64
  }
65
65
  });
66
+ Object.defineProperty(exports, "NetworkHeaderBadgesView", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _NetworkHeaderLive.NetworkHeaderBadgesView;
70
+ }
71
+ });
66
72
  Object.defineProperty(exports, "NetworkModal", {
67
73
  enumerable: true,
68
74
  get: function () {
@@ -546,62 +546,16 @@ function NetworkCopySettingsView({
546
546
  const generatePreviewContent = (0, _react.useCallback)(() => {
547
547
  const dataSource = hasLiveData ? events : [mockRequestData];
548
548
  const requestCount = dataSource.length;
549
-
550
- // Collapsed state - show summary with expand button
551
- if (!isPreviewExpanded) {
552
- const warningColor = sizeWarningLevel === 'danger' ? _sharedUi.macOSColors.semantic.error : sizeWarningLevel === 'warning' ? _sharedUi.macOSColors.semantic.warning : _sharedUi.macOSColors.text.secondary;
553
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
554
- style: styles.collapsedPreview,
555
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
556
- style: styles.expandButton,
557
- onPress: () => setIsPreviewExpanded(true),
558
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
559
- style: styles.expandButtonContent,
560
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Eye, {
561
- size: 16,
562
- color: _sharedUi.macOSColors.semantic.info
563
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
564
- style: styles.expandButtonTextContainer,
565
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
566
- style: styles.expandButtonTitle,
567
- children: "Show Preview"
568
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
569
- style: styles.expandButtonSubtitle,
570
- children: [requestCount, " request", requestCount !== 1 ? 's' : '', " \u2022 ~", formatBytes(estimatedSize)]
571
- })]
572
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
573
- size: 16,
574
- color: _sharedUi.macOSColors.text.muted
575
- })]
576
- })
577
- }), sizeWarningLevel !== 'none' && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
578
- style: [styles.sizeWarning, sizeWarningLevel === 'danger' && styles.sizeWarningDanger],
579
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
580
- size: 12,
581
- color: warningColor
582
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
583
- style: [styles.sizeWarningText, {
584
- color: warningColor
585
- }],
586
- children: sizeWarningLevel === 'danger' ? 'Very large payload - may cause performance issues' : 'Large payload - preview may be slow'
587
- })]
588
- })]
589
- });
590
- }
591
-
592
- // Expanded state - show collapse button + actual content
593
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
594
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
595
- style: styles.collapseButton,
596
- onPress: () => setIsPreviewExpanded(false),
597
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
598
- style: styles.collapseButtonText,
599
- children: "Hide Preview"
600
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronUp, {
601
- size: 14,
602
- color: _sharedUi.macOSColors.text.secondary
603
- })]
604
- }), generateExpandedPreviewContent()]
549
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ExportPreview, {
550
+ expanded: isPreviewExpanded,
551
+ onExpandedChange: setIsPreviewExpanded,
552
+ summary: `${requestCount} request${requestCount !== 1 ? "s" : ""} • ~${formatBytes(estimatedSize)}`,
553
+ warningLevel: sizeWarningLevel,
554
+ warningText: {
555
+ warning: "Large payload - preview may be slow",
556
+ danger: "Very large payload - may cause performance issues"
557
+ },
558
+ renderBody: generateExpandedPreviewContent
605
559
  });
606
560
  }, [settings, hasLiveData, events, mockRequestData, isPreviewExpanded, estimatedSize, sizeWarningLevel, generateExpandedPreviewContent]);
607
561
 
@@ -790,71 +744,6 @@ const styles = _reactNative.StyleSheet.create({
790
744
  marginRight: 8
791
745
  },
792
746
  // Collapsible preview styles
793
- collapsedPreview: {
794
- gap: 8
795
- },
796
- expandButton: {
797
- backgroundColor: _sharedUi.macOSColors.background.input,
798
- borderRadius: 8,
799
- borderWidth: 1,
800
- borderColor: _sharedUi.macOSColors.border.default,
801
- padding: 12
802
- },
803
- expandButtonContent: {
804
- flexDirection: 'row',
805
- alignItems: 'center',
806
- gap: 10
807
- },
808
- expandButtonTextContainer: {
809
- flex: 1
810
- },
811
- expandButtonTitle: {
812
- fontSize: 13,
813
- fontWeight: '600',
814
- color: _sharedUi.macOSColors.text.primary
815
- },
816
- expandButtonSubtitle: {
817
- fontSize: 11,
818
- color: _sharedUi.macOSColors.text.secondary,
819
- marginTop: 2
820
- },
821
- collapseButton: {
822
- flexDirection: 'row',
823
- alignItems: 'center',
824
- justifyContent: 'center',
825
- gap: 6,
826
- paddingVertical: 8,
827
- marginBottom: 8,
828
- backgroundColor: _sharedUi.macOSColors.background.hover,
829
- borderRadius: 6,
830
- borderWidth: 1,
831
- borderColor: _sharedUi.macOSColors.border.default
832
- },
833
- collapseButtonText: {
834
- fontSize: 12,
835
- fontWeight: '500',
836
- color: _sharedUi.macOSColors.text.secondary
837
- },
838
- sizeWarning: {
839
- flexDirection: 'row',
840
- alignItems: 'center',
841
- gap: 6,
842
- paddingHorizontal: 10,
843
- paddingVertical: 6,
844
- backgroundColor: _sharedUi.macOSColors.semantic.warningBackground,
845
- borderRadius: 6,
846
- borderWidth: 1,
847
- borderColor: _sharedUi.macOSColors.semantic.warning + '30'
848
- },
849
- sizeWarningDanger: {
850
- backgroundColor: _sharedUi.macOSColors.semantic.errorBackground,
851
- borderColor: _sharedUi.macOSColors.semantic.error + '30'
852
- },
853
- sizeWarningText: {
854
- fontSize: 11,
855
- fontWeight: '500',
856
- flex: 1
857
- },
858
747
  // Gated container styles for free users
859
748
  gatedContainer: {
860
749
  flex: 1,
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.NetworkClearButton = NetworkClearButton;
7
7
  exports.NetworkDetailPinActions = NetworkDetailPinActions;
8
8
  exports.NetworkHeaderBadges = NetworkHeaderBadges;
9
+ exports.NetworkHeaderBadgesView = NetworkHeaderBadgesView;
9
10
  exports.NetworkHeaderCopyButton = NetworkHeaderCopyButton;
10
11
  exports.NetworkSavedButton = NetworkSavedButton;
11
12
  var _reactNative = require("react-native");
@@ -48,6 +49,21 @@ function NetworkHeaderBadges({
48
49
  ignoredPatterns,
49
50
  paused
50
51
  });
52
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkHeaderBadgesView, {
53
+ counts: counts,
54
+ activeStatus: activeStatus,
55
+ onToggleStatus: onToggleStatus
56
+ });
57
+ }
58
+ /**
59
+ * The chips themselves, fed by props — what the parity sheet renders with
60
+ * fixture counts (`NetworkHeaderBadges` reads the live store).
61
+ */
62
+ function NetworkHeaderBadgesView({
63
+ counts,
64
+ activeStatus,
65
+ onToggleStatus
66
+ }) {
51
67
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
52
68
  style: styles.headerChipRow,
53
69
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
@@ -27,23 +27,6 @@ var _jsxRuntime = require("react/jsx-runtime");
27
27
  * `paused || !visible` so a hidden list doesn't subscribe at all.
28
28
  */
29
29
 
30
- function EmptyState({
31
- isEnabled
32
- }) {
33
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
34
- style: styles.emptyState,
35
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Globe, {
36
- size: 32,
37
- color: _sharedUi.macOSColors.text.muted
38
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
39
- style: styles.emptyTitle,
40
- children: "No network events"
41
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
42
- style: styles.emptyText,
43
- children: isEnabled ? "Network requests will appear here" : "Enable interception to start capturing"
44
- })]
45
- });
46
- }
47
30
  const keyExtractor = item => item.id;
48
31
 
49
32
  /**
@@ -164,7 +147,8 @@ function NetworkListScreen({
164
147
  const {
165
148
  visibleEvents,
166
149
  hasLockedEvents,
167
- lockedEventCount
150
+ lockedEventCount,
151
+ totalEvents
168
152
  } = (0, _useNetworkEventList.useNetworkEventList)({
169
153
  filter,
170
154
  ignoredPatterns,
@@ -226,6 +210,34 @@ function NetworkListScreen({
226
210
  onLongPress: handleLongPress,
227
211
  saved: savedLiveIds.has(item.id)
228
212
  });
213
+
214
+ // Which flavour of "nothing here" this is. Pins and overrides render
215
+ // outside the list, so a screen showing only those is not empty at all.
216
+ const renderEmpty = () => {
217
+ // Pins and overrides render outside the list, so a screen showing only
218
+ // those is not empty at all.
219
+ if (pinnedRows.length > 0 || overridden.length > 0) return null;
220
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
221
+ testID: "buoy-network-empty",
222
+ ...(0, _sharedUi.resolveListEmptyState)({
223
+ subject: "requests",
224
+ search: searchText,
225
+ total: totalEvents,
226
+ capturing: isEnabled,
227
+ waiting: {
228
+ // fishing = capture is on and nothing has arrived.
229
+ otto: "fishing",
230
+ title: "No network events",
231
+ message: "Network requests will appear here"
232
+ },
233
+ paused: {
234
+ // paused = capture off. Both are literally what the copy says.
235
+ title: "No network events",
236
+ message: "Enable interception to start capturing"
237
+ }
238
+ })
239
+ });
240
+ };
229
241
  const renderListFooter = () => {
230
242
  if (!hasLockedEvents) return null;
231
243
  const isSearching = searchText.length > 0;
@@ -270,10 +282,8 @@ function NetworkListScreen({
270
282
  showsVerticalScrollIndicator: true
271
283
  // Clipped cells falsely fail Detox's hittability check (known
272
284
  // RN+Detox issue), so e2e bundles (Metro started with
273
- // EXPO_PUBLIC_E2E=1) turn clipping off. Inlined at bundle time;
274
- // guarded so a non-inlining bundler keeps the prod behavior.
285
+ // EXPO_PUBLIC_E2E=1) turn clipping off — see EVENT_LIST_PRESET.
275
286
  ,
276
- removeClippedSubviews: !(typeof process !== "undefined" && process.env?.EXPO_PUBLIC_E2E),
277
287
  onEndReachedThreshold: 0.8
278
288
  // The list OWNS scrolling (NetworkModal passes
279
289
  // disableScrollWrapper while this screen is visible) so FlatList
@@ -282,24 +292,18 @@ function NetworkListScreen({
282
292
  // rows: at the store's 500-event cap that measured 17 JS FPS /
283
293
  // 133% CPU / 1.4GB vs ~53 JS FPS / 52% / 360MB virtualized
284
294
  // (buoy-optimize batch 2026-07-06, batch-1783396983529-6l7w).
285
- // Window/batch values are the batch's "tuned" winner.
295
+ // Window/batch values are the batch's "tuned" winner (EVENT_LIST_PRESET).
286
296
  ,
287
297
  scrollEnabled: true,
288
- initialNumToRender: 12,
289
- maxToRenderPerBatch: 8,
290
- windowSize: 5,
291
- updateCellsBatchingPeriod: 50
298
+ ..._sharedUi.EVENT_LIST_PRESET,
292
299
  // Saving a request from the detail view changes a row's glyph
293
300
  // without changing `data`. VirtualizedList is a PureComponent, so
294
301
  // the Set has to be declared as a re-render marker rather than
295
302
  // relying on `renderItem` getting a fresh identity.
296
- ,
297
303
  extraData: savedLiveIds,
298
304
  "sentry-label": "ignore network events list",
299
305
  ListFooterComponent: renderListFooter
300
- }) : pinnedRows.length > 0 || overridden.length > 0 ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {
301
- isEnabled: isEnabled
302
- })]
306
+ }) : renderEmpty()]
303
307
  });
304
308
  }
305
309
  const styles = _reactNative.StyleSheet.create({
@@ -396,21 +400,5 @@ const styles = _reactNative.StyleSheet.create({
396
400
  },
397
401
  listContent: {
398
402
  paddingTop: 8
399
- },
400
- emptyState: {
401
- alignItems: "center",
402
- paddingVertical: 40
403
- },
404
- emptyTitle: {
405
- color: _sharedUi.macOSColors.text.primary,
406
- fontSize: 14,
407
- fontWeight: "600",
408
- marginTop: 12,
409
- marginBottom: 6
410
- },
411
- emptyText: {
412
- color: _sharedUi.macOSColors.text.muted,
413
- fontSize: 12,
414
- textAlign: "center"
415
403
  }
416
404
  });
@@ -235,6 +235,12 @@ function NetworkModalInner({
235
235
  const [activeTab, setActiveTab] = (0, _react.useState)("filters");
236
236
  const [searchText, setSearchText] = (0, _react.useState)("");
237
237
  const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
238
+ // True while the header's clear ✕ is being pressed. With a mouse, pressing
239
+ // the ✕ blurs the input FIRST, and the blur handler below closes search —
240
+ // which unmounts the ✕ before its own press can fire, so the query was
241
+ // never cleared (desktop). Press-in lands before the blur, so the blur
242
+ // handler can tell this dismissal apart from a click elsewhere.
243
+ const clearPressRef = (0, _react.useRef)(false);
238
244
  // The Saved screen's search box lives HERE, not in that screen, because the
239
245
  // detail footer steps through the same list the user was just looking at —
240
246
  // and the Saved screen is unmounted while a detail is open.
@@ -446,38 +452,24 @@ function NetworkModalInner({
446
452
  onBack: onBack
447
453
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
448
454
  title: "",
449
- children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
450
- style: styles.headerSearchContainer,
451
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
452
- size: 14,
453
- color: _sharedUi.night.color.textSecondary
454
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
455
- ref: searchInputRef,
456
- style: styles.headerSearchInput,
457
- placeholder: "Search URL, method, operation, error...",
458
- placeholderTextColor: _sharedUi.night.color.placeholder,
459
- value: searchText,
460
- onChangeText: handleSearch,
461
- onSubmitEditing: () => setIsSearchActive(false),
462
- onBlur: () => setIsSearchActive(false),
463
- "sentry-label": "ignore network search header",
464
- accessibilityLabel: "Search network requests",
465
- testID: "buoy-network-search-input",
466
- autoCapitalize: "none",
467
- autoCorrect: false,
468
- returnKeyType: "search"
469
- }), searchText.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
470
- onPress: () => {
471
- handleSearch("");
472
- setIsSearchActive(false);
473
- },
474
- "sentry-label": "ignore clear search header",
475
- style: styles.headerSearchClear,
476
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
477
- size: 14,
478
- color: _sharedUi.night.color.textSecondary
479
- })
480
- }) : null]
455
+ children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Search, {
456
+ value: searchText,
457
+ onChangeText: handleSearch,
458
+ onCancel: () => {
459
+ handleSearch("");
460
+ setIsSearchActive(false);
461
+ },
462
+ onDismiss: () => setIsSearchActive(false),
463
+ dismissOnBlur: true,
464
+ clearOnlyWhenText: true,
465
+ placeholder: "Search URL, method, operation, error...",
466
+ accessibilityLabel: "Search network requests",
467
+ iconSize: 14,
468
+ testID: "buoy-network-search-input",
469
+ clearTestID: "buoy-network-search-clear",
470
+ inputProps: {
471
+ "sentry-label": "ignore network search header"
472
+ }
481
473
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkHeaderLive.NetworkHeaderBadges, {
482
474
  activeStatus: filter.status,
483
475
  onToggleStatus: toggleStatusFilter,
@@ -722,27 +714,6 @@ const styles = _reactNative.StyleSheet.create({
722
714
  },
723
715
  // Night chrome — the header is glass over the star field, so the search
724
716
  // field and action chips sit on the Everlights dark-glass fill.
725
- headerSearchContainer: {
726
- flexDirection: "row",
727
- alignItems: "center",
728
- backgroundColor: _sharedUi.night.color.surfaceElevated,
729
- borderRadius: _sharedUi.night.radius.row,
730
- borderWidth: 1,
731
- borderColor: _sharedUi.night.color.border,
732
- paddingHorizontal: 12,
733
- paddingVertical: 5
734
- },
735
- headerSearchInput: {
736
- flex: 1,
737
- color: _sharedUi.night.color.text,
738
- fontSize: 13,
739
- marginLeft: 6,
740
- paddingVertical: 2
741
- },
742
- headerSearchClear: {
743
- marginLeft: 6,
744
- padding: 4
745
- },
746
717
  menuCopyRow: {
747
718
  paddingHorizontal: 6
748
719
  },
@@ -365,8 +365,17 @@ function OverridesBody({
365
365
  })]
366
366
  }), !hasRules ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
367
367
  style: styles.emptyWrap,
368
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EmptyState, {
369
- icon: _sharedUi.FlaskConical,
368
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EmptyState
369
+ // Otto instead of the flask: nothing is faked yet, so he peers
370
+ // into an empty box. Truthful by construction — this branch only
371
+ // renders when the rule list is genuinely empty, and the master
372
+ // switch bar above is hidden in exactly that case.
373
+ , {
374
+ otto: "empty"
375
+ // 32 → pixelSize 2, the size the saved/list screens use, so the
376
+ // three network empties read as one family.
377
+ ,
378
+ iconSize: 32,
370
379
  title: "Nothing overridden"
371
380
  // Names the control that actually exists. This said "tap Force 500"
372
381
  // — a button from the deleted preset list, which sent anyone
@@ -32,21 +32,6 @@ var _jsxRuntime = require("react/jsx-runtime");
32
32
  */
33
33
 
34
34
  const keyExtractor = item => item.id;
35
- function EmptyState() {
36
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
37
- style: styles.emptyState,
38
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Bookmark, {
39
- size: 32,
40
- color: _sharedUi.macOSColors.text.muted
41
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
42
- style: styles.emptyTitle,
43
- children: "No saved requests"
44
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
45
- style: styles.emptyText,
46
- children: "Open a request and tap the bookmark to keep it here \u2014 saved requests survive Clear and app restarts."
47
- })]
48
- });
49
- }
50
35
  function NetworkSavedScreen({
51
36
  visible,
52
37
  isPro,
@@ -146,17 +131,31 @@ function NetworkSavedScreen({
146
131
  autoCorrect: false,
147
132
  returnKeyType: "search"
148
133
  })]
149
- }) : null, savedRecords.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
134
+ }) : null, savedRecords.length === 0 ||
135
+ // lockedCount > 0 falls through to the list so the upgrade banner (its
136
+ // ListFooterComponent) still renders — "0 shown, N locked" is an
137
+ // answer, not an empty screen.
138
+ search.trim().length > 0 && filtered.length === 0 && lockedCount === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
139
+ testID: "buoy-network-saved-empty",
140
+ ...(0, _sharedUi.resolveListEmptyState)({
141
+ subject: "saved requests",
142
+ verb: "kept",
143
+ search: savedRecords.length === 0 ? "" : search,
144
+ total: savedRecords.length,
145
+ waiting: {
146
+ otto: "empty",
147
+ title: "No saved requests",
148
+ message: "Open a request and tap the bookmark to keep it here — saved requests survive Clear and app restarts."
149
+ }
150
+ })
151
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
150
152
  data: filtered,
151
153
  renderItem: renderItem,
152
154
  keyExtractor: keyExtractor,
153
155
  contentContainerStyle: styles.listContent,
154
156
  showsVerticalScrollIndicator: true,
155
- removeClippedSubviews: true,
156
157
  scrollEnabled: true,
157
- initialNumToRender: 12,
158
- maxToRenderPerBatch: 8,
159
- windowSize: 5,
158
+ ..._sharedUi.EVENT_LIST_PRESET,
160
159
  extraData: pinnedLiveIds,
161
160
  "sentry-label": "ignore saved requests list",
162
161
  ListFooterComponent: renderListFooter
@@ -223,23 +222,5 @@ const styles = _reactNative.StyleSheet.create({
223
222
  },
224
223
  listContent: {
225
224
  paddingTop: 4
226
- },
227
- emptyState: {
228
- alignItems: "center",
229
- paddingVertical: 40,
230
- paddingHorizontal: 24
231
- },
232
- emptyTitle: {
233
- color: _sharedUi.macOSColors.text.primary,
234
- fontSize: 14,
235
- fontWeight: "600",
236
- marginTop: 12,
237
- marginBottom: 6
238
- },
239
- emptyText: {
240
- color: _sharedUi.macOSColors.text.muted,
241
- fontSize: 12,
242
- textAlign: "center",
243
- lineHeight: 17
244
225
  }
245
226
  });
@@ -78,9 +78,11 @@ function useNetworkEventList(options) {
78
78
 
79
79
  // Derivations are plain useMemo over the subscribed array — linear
80
80
  // passes over ≤maxEvents items, recomputed only when inputs change.
81
- const filteredEvents = (0, _react.useMemo)(() => {
82
- return (0, _filterNetworkEvents.filterNetworkEvents)((0, _filterNetworkEvents.applyIgnoredPatterns)(events, ignoredPatterns), filter);
83
- }, [events, ignoredPatterns, filter]);
81
+ // Split in two so the pre-filter count is observable: `totalEvents` below
82
+ // is the denominator the empty state needs, and recomputing it separately
83
+ // would mean a second pass over the same array.
84
+ const candidateEvents = (0, _react.useMemo)(() => (0, _filterNetworkEvents.applyIgnoredPatterns)(events, ignoredPatterns), [events, ignoredPatterns]);
85
+ const filteredEvents = (0, _react.useMemo)(() => (0, _filterNetworkEvents.filterNetworkEvents)(candidateEvents, filter), [candidateEvents, filter]);
84
86
 
85
87
  // Free tier: events beyond the limit in the ORIGINAL order are locked,
86
88
  // so locked events stay locked even when searching (ported verbatim).
@@ -100,6 +102,7 @@ function useNetworkEventList(options) {
100
102
  events: filteredEvents,
101
103
  visibleEvents,
102
104
  hasLockedEvents: lockedEventCount > 0,
103
- lockedEventCount
105
+ lockedEventCount,
106
+ totalEvents: candidateEvents.length
104
107
  };
105
108
  }
@@ -431,6 +431,14 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
431
431
  const event = resolveEvent(id);
432
432
  if (!event) return null;
433
433
  return {
434
+ // Identity + the override mark ride along so a caller (Ask Buoy's
435
+ // receipt, an MCP session) can say WHOSE body this is and whether an
436
+ // override rule produced it — a mocked response rendered as "live"
437
+ // is the fabrication problem wearing a chrome suit.
438
+ url: event.url ?? null,
439
+ method: event.method ?? null,
440
+ status: event.status ?? null,
441
+ override: event.override ?? null,
434
442
  requestData: event.requestData ?? null,
435
443
  responseData: event.responseData ?? null,
436
444
  requestHeaders: event.requestHeaders ?? null,
@@ -3,8 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.createNetworkTool = createNetworkTool;
7
- exports.networkToolPreset = void 0;
6
+ exports.networkToolPreset = exports.createNetworkTool = void 0;
8
7
  var _sharedUi = require("@buoy-gg/shared-ui");
9
8
  var _NetworkModal = require("./network/components/NetworkModal");
10
9
  var _overrideRulesStore = require("./network/overrides/overrideRulesStore");
@@ -66,7 +65,7 @@ if (isReactNativeRuntime && typeof __DEV__ !== "undefined" && __DEV__) {
66
65
  * - Filter by status, method, URL
67
66
  * - Timing information
68
67
  */
69
- const networkToolPreset = exports.networkToolPreset = {
68
+ const networkToolPreset = exports.networkToolPreset = (0, _sharedUi.defineToolPreset)({
70
69
  id: "network",
71
70
  name: "NET",
72
71
  description: "Network request logger",
@@ -80,7 +79,7 @@ const networkToolPreset = exports.networkToolPreset = {
80
79
  props: {
81
80
  enableSharedModalDimensions: false
82
81
  }
83
- };
82
+ });
84
83
 
85
84
  /**
86
85
  * Create a custom network monitoring tool configuration.
@@ -97,20 +96,6 @@ const networkToolPreset = exports.networkToolPreset = {
97
96
  * });
98
97
  * ```
99
98
  */
100
- function createNetworkTool(options) {
101
- return {
102
- id: options?.id || "network",
103
- name: options?.name || "NET",
104
- description: options?.description || "Network request logger",
105
- slot: "both",
106
- icon: ({
107
- size
108
- }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NetworkIcon, {
109
- size: size
110
- }),
111
- component: _NetworkModal.NetworkModal,
112
- props: {
113
- enableSharedModalDimensions: options?.enableSharedModalDimensions !== undefined ? options.enableSharedModalDimensions : false
114
- }
115
- };
116
- }
99
+ const createNetworkTool = exports.createNetworkTool = (0, _sharedUi.createToolFactory)(networkToolPreset, options => ({
100
+ enableSharedModalDimensions: options.enableSharedModalDimensions ?? false
101
+ }));
@@ -24,7 +24,7 @@ export { NetworkModal, NetworkToolView } from "./network/components/NetworkModal
24
24
  export { NetworkEventDetailView } from "./network/components/NetworkEventDetailView";
25
25
  export { NetworkEventItemCompact } from "./network/components/NetworkEventItemCompact";
26
26
  export { NetworkSavedScreen } from "./network/components/NetworkSavedScreen";
27
- export { NetworkSavedButton, NetworkDetailPinActions } from "./network/components/NetworkHeaderLive";
27
+ export { NetworkSavedButton, NetworkDetailPinActions, NetworkHeaderBadgesView } from "./network/components/NetworkHeaderLive";
28
28
 
29
29
  // =============================================================================
30
30
  // HOOKS (For consuming network data in custom components)