@buoy-gg/network 4.0.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/lib/commonjs/index.js +14 -0
  2. package/lib/commonjs/network/components/NetworkEventItemCompact.js +32 -18
  3. package/lib/commonjs/network/components/NetworkHeaderLive.js +199 -0
  4. package/lib/commonjs/network/components/NetworkListScreen.js +203 -0
  5. package/lib/commonjs/network/components/NetworkModal.js +153 -630
  6. package/lib/commonjs/network/hooks/useNetworkBadgeCounts.js +83 -0
  7. package/lib/commonjs/network/hooks/useNetworkEventList.js +93 -0
  8. package/lib/commonjs/network/hooks/useNetworkHasMatches.js +56 -0
  9. package/lib/commonjs/network/utils/filterNetworkEvents.js +96 -0
  10. package/lib/commonjs/network/utils/generateNetworkCopyText.js +139 -0
  11. package/lib/module/index.js +5 -0
  12. package/lib/module/network/components/NetworkEventItemCompact.js +32 -18
  13. package/lib/module/network/components/NetworkHeaderLive.js +193 -0
  14. package/lib/module/network/components/NetworkListScreen.js +199 -0
  15. package/lib/module/network/components/NetworkModal.js +157 -634
  16. package/lib/module/network/hooks/useNetworkBadgeCounts.js +79 -0
  17. package/lib/module/network/hooks/useNetworkEventList.js +89 -0
  18. package/lib/module/network/hooks/useNetworkHasMatches.js +52 -0
  19. package/lib/module/network/utils/filterNetworkEvents.js +92 -0
  20. package/lib/module/network/utils/generateNetworkCopyText.js +135 -0
  21. package/lib/typescript/index.d.ts +2 -0
  22. package/lib/typescript/index.d.ts.map +1 -1
  23. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +0 -4
  24. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
  25. package/lib/typescript/network/components/NetworkHeaderLive.d.ts +52 -0
  26. package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -0
  27. package/lib/typescript/network/components/NetworkListScreen.d.ts +27 -0
  28. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -0
  29. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  30. package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts +24 -0
  31. package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts.map +1 -0
  32. package/lib/typescript/network/hooks/useNetworkEventList.d.ts +34 -0
  33. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -0
  34. package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts +23 -0
  35. package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts.map +1 -0
  36. package/lib/typescript/network/utils/filterNetworkEvents.d.ts +13 -0
  37. package/lib/typescript/network/utils/filterNetworkEvents.d.ts.map +1 -0
  38. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +11 -0
  39. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -0
  40. package/package.json +2 -2
@@ -117,6 +117,18 @@ Object.defineProperty(exports, "useIgnoredPatterns", {
117
117
  return _sharedUi.useIgnoredPatterns;
118
118
  }
119
119
  });
120
+ Object.defineProperty(exports, "useNetworkBadgeCounts", {
121
+ enumerable: true,
122
+ get: function () {
123
+ return _useNetworkBadgeCounts.useNetworkBadgeCounts;
124
+ }
125
+ });
126
+ Object.defineProperty(exports, "useNetworkEventList", {
127
+ enumerable: true,
128
+ get: function () {
129
+ return _useNetworkEventList.useNetworkEventList;
130
+ }
131
+ });
120
132
  Object.defineProperty(exports, "useNetworkEvents", {
121
133
  enumerable: true,
122
134
  get: function () {
@@ -134,6 +146,8 @@ var _NetworkModal = require("./network/components/NetworkModal");
134
146
  var _NetworkEventDetailView = require("./network/components/NetworkEventDetailView");
135
147
  var _NetworkEventItemCompact = require("./network/components/NetworkEventItemCompact");
136
148
  var _useNetworkEvents = require("./network/hooks/useNetworkEvents");
149
+ var _useNetworkEventList = require("./network/hooks/useNetworkEventList");
150
+ var _useNetworkBadgeCounts = require("./network/hooks/useNetworkBadgeCounts");
137
151
  var _useTickEveryMinute = require("./network/hooks/useTickEveryMinute");
138
152
  var _formatting = require("./network/utils/formatting");
139
153
  var _sharedUi = require("@buoy-gg/shared-ui");
@@ -112,11 +112,33 @@ function SizeIndicators({
112
112
  * Compact list-row representation of a network event. Optimized for large lists with memoization
113
113
  * and periodic refresh via `useTickEveryMinute` to keep relative timestamps accurate.
114
114
  */
115
- const NetworkEventItemCompact = exports.NetworkEventItemCompact = /*#__PURE__*/(0, _react.memo)(({
115
+ /**
116
+ * Sole tick-context consumer in the row (rule2: composition over memo) —
117
+ * the periodic relative-timestamp tick re-renders ONLY this Text, not the
118
+ * whole card. Profiling (tool-bench 2026-07-06): the tick accounted for
119
+ * ~30 of 65 row renders (plus their SVG-icon churn) in a 12s recording.
120
+ */
121
+ function BottomRightTime({
122
+ timestamp,
123
+ duration
124
+ }) {
125
+ const tick = (0, _useTickEveryMinute.useTickEveryMinute)();
126
+ const relativeTime = (0, _sharedUi.formatRelativeTime)(timestamp, tick);
127
+ return duration ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
128
+ style: styles.bottomRightText,
129
+ children: [(0, _formatting.formatDuration)(duration), " ", relativeTime]
130
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
131
+ style: styles.bottomRightText,
132
+ children: relativeTime
133
+ });
134
+ }
135
+ const NetworkEventItemCompact = exports.NetworkEventItemCompact = /*#__PURE__*/(0, _react.memo)(
136
+ // Named function (not an arrow) so render-capture reports attribute row
137
+ // renders to this component instead of a compiled anonymous name.
138
+ function NetworkEventItemCompact({
116
139
  event,
117
140
  onPress
118
- }) => {
119
- const tick = (0, _useTickEveryMinute.useTickEveryMinute)();
141
+ }) {
120
142
  const statusColor = getStatusColor(event.status, event.error);
121
143
  const isPending = !event.status && !event.error;
122
144
  const contentType = getContentTypeBadge(event.responseHeaders);
@@ -138,15 +160,6 @@ const NetworkEventItemCompact = exports.NetworkEventItemCompact = /*#__PURE__*/(
138
160
  // For non-GraphQL requests with operation names (e.g., gRPC)
139
161
  displayUrl = `${displayUrl}\n(${event.operationName})`;
140
162
  }
141
-
142
- // Format time with both absolute and relative
143
- const timeString = new Date(event.timestamp).toLocaleTimeString("en-US", {
144
- hour: "numeric",
145
- minute: "2-digit",
146
- second: "2-digit",
147
- hour12: true
148
- });
149
- const relativeTime = (0, _sharedUi.formatRelativeTime)(event.timestamp, tick);
150
163
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.DevToolsCard, {
151
164
  onPress: () => onPress(event),
152
165
  style: styles.cardWrapper,
@@ -197,16 +210,17 @@ const NetworkEventItemCompact = exports.NetworkEventItemCompact = /*#__PURE__*/(
197
210
  })]
198
211
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
199
212
  style: styles.bottomRightContainer,
200
- children: event.duration ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
201
- style: styles.bottomRightText,
202
- children: [(0, _formatting.formatDuration)(event.duration), " ", relativeTime]
203
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
204
- style: styles.bottomRightText,
205
- children: relativeTime
213
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(BottomRightTime, {
214
+ timestamp: event.timestamp,
215
+ duration: event.duration
206
216
  })
207
217
  })]
208
218
  });
209
219
  });
220
+
221
+ // Without this, render-capture reports attribute row renders to the
222
+ // anonymous memo wrapper ("Memo"/"_c4") instead of the row.
223
+ NetworkEventItemCompact.displayName = "NetworkEventItemCompact";
210
224
  const styles = _reactNative.StyleSheet.create({
211
225
  cardWrapper: {
212
226
  marginHorizontal: 8
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkClearButton = NetworkClearButton;
7
+ exports.NetworkHeaderBadges = NetworkHeaderBadges;
8
+ exports.NetworkHeaderCopyButton = NetworkHeaderCopyButton;
9
+ var _reactNative = require("react-native");
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
+ var _networkEventStore = require("../utils/networkEventStore");
12
+ var _useNetworkBadgeCounts = require("../hooks/useNetworkBadgeCounts");
13
+ var _useNetworkHasMatches = require("../hooks/useNetworkHasMatches");
14
+ var _generateNetworkCopyText = require("../utils/generateNetworkCopyText");
15
+ var _filterNetworkEvents = require("../utils/filterNetworkEvents");
16
+ var _jsxRuntime = require("react/jsx-runtime");
17
+ /**
18
+ * The header pieces that consume LIVE event data, decomposed out of
19
+ * NetworkModal per rule3 (decompose by responsibility) so each one is its
20
+ * own narrow store subscriber and the modal root/header never re-render
21
+ * per network event:
22
+ *
23
+ * - `NetworkHeaderBadges` — success/error/pending count chips
24
+ * - `NetworkClearButton` — trash button (enabled = store has events;
25
+ * clear handler co-located here per rule2)
26
+ * - `NetworkHeaderCopyButton` — copy-all button; the copy TEXT is
27
+ * resolved lazily at press time (CopyButton accepts a function value —
28
+ * rule2's latest-ref pattern), so no string is built per event
29
+ *
30
+ * Profiling evidence for this decomposition: tool-bench 2026-07-06 —
31
+ * Trash2/CopyButton re-rendered 15× for 15 network events when the
32
+ * subscription lived at the modal root.
33
+ */
34
+
35
+ /** Success / error / pending chips — tap to toggle the status filter. */
36
+ function NetworkHeaderBadges({
37
+ activeStatus,
38
+ onToggleStatus,
39
+ ignoredPatterns,
40
+ paused
41
+ }) {
42
+ const counts = (0, _useNetworkBadgeCounts.useNetworkBadgeCounts)({
43
+ ignoredPatterns,
44
+ paused
45
+ });
46
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
47
+ style: styles.headerChipRow,
48
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
49
+ style: [styles.headerChip, activeStatus === "success" && styles.headerChipActive],
50
+ onPress: () => onToggleStatus("success"),
51
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle, {
52
+ size: 12,
53
+ color: _sharedUi.macOSColors.semantic.success
54
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
55
+ style: [styles.headerChipValue, {
56
+ color: _sharedUi.macOSColors.semantic.success
57
+ }],
58
+ children: counts.successful
59
+ })]
60
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
61
+ style: [styles.headerChip, activeStatus === "error" && styles.headerChipActive],
62
+ onPress: () => onToggleStatus("error"),
63
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.XCircle, {
64
+ size: 12,
65
+ color: _sharedUi.macOSColors.semantic.error
66
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
67
+ style: [styles.headerChipValue, {
68
+ color: _sharedUi.macOSColors.semantic.error
69
+ }],
70
+ children: counts.failed
71
+ })]
72
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
73
+ style: [styles.headerChip, activeStatus === "pending" && styles.headerChipActive],
74
+ onPress: () => onToggleStatus("pending"),
75
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Clock, {
76
+ size: 12,
77
+ color: _sharedUi.macOSColors.semantic.warning
78
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
79
+ style: [styles.headerChipValue, {
80
+ color: _sharedUi.macOSColors.semantic.warning
81
+ }],
82
+ children: counts.pending
83
+ })]
84
+ })]
85
+ });
86
+ }
87
+ /** Trash button — handler co-located; enabled while the store has events.
88
+ * Subscribes to a flip-guarded boolean, so it renders when the store goes
89
+ * empty↔non-empty, not on every count change. */
90
+ function NetworkClearButton({
91
+ paused
92
+ }) {
93
+ const hasEvents = (0, _useNetworkHasMatches.useNetworkHasEvents)({
94
+ paused
95
+ });
96
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
97
+ "sentry-label": "ignore clear events",
98
+ onPress: () => _networkEventStore.networkEventStore.clearEvents(),
99
+ style: [styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
100
+ disabled: !hasEvents,
101
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
102
+ size: 14,
103
+ color: hasEvents ? _sharedUi.macOSColors.text.muted : _sharedUi.macOSColors.text.disabled
104
+ })
105
+ });
106
+ }
107
+ /**
108
+ * Copy-all-requests button. Subscribes to a flip-guarded boolean (its
109
+ * enabled state); the copy text is generated at PRESS time from a live
110
+ * store read, so there's no per-event work and no stale closure.
111
+ */
112
+ function NetworkHeaderCopyButton({
113
+ isPro,
114
+ filter,
115
+ ignoredPatterns,
116
+ copySettings,
117
+ paused,
118
+ onUpgradePress
119
+ }) {
120
+ const hasEvents = (0, _useNetworkHasMatches.useNetworkHasMatches)({
121
+ filter,
122
+ ignoredPatterns,
123
+ paused
124
+ });
125
+ if (!isPro) {
126
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
127
+ "sentry-label": "ignore copy pro",
128
+ onPress: onUpgradePress,
129
+ style: [styles.headerActionButton, !hasEvents && styles.headerActionButtonDisabled],
130
+ disabled: !hasEvents,
131
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Copy, {
132
+ size: 14,
133
+ color: hasEvents ? _sharedUi.macOSColors.text.secondary : _sharedUi.macOSColors.text.disabled
134
+ })
135
+ });
136
+ }
137
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton
138
+ // Lazy value: resolved at copy time by CopyButton's latest-ref
139
+ // pattern, reading the LIVE store — no per-event work, no staleness.
140
+ , {
141
+ value: () => (0, _generateNetworkCopyText.generateNetworkCopyText)((0, _filterNetworkEvents.filterNetworkEvents)((0, _filterNetworkEvents.applyIgnoredPatterns)(_networkEventStore.networkEventStore.getEvents(), ignoredPatterns), filter), copySettings),
142
+ size: 14,
143
+ buttonStyle: styles.headerActionButton,
144
+ colors: {
145
+ idle: hasEvents ? _sharedUi.macOSColors.text.secondary : _sharedUi.macOSColors.text.disabled,
146
+ success: _sharedUi.macOSColors.semantic.success,
147
+ error: _sharedUi.macOSColors.semantic.error
148
+ },
149
+ disabled: !hasEvents
150
+ });
151
+ }
152
+ const styles = _reactNative.StyleSheet.create({
153
+ headerChipRow: {
154
+ flexDirection: "row",
155
+ alignItems: "center",
156
+ gap: 8
157
+ },
158
+ headerChip: {
159
+ flexDirection: "row",
160
+ alignItems: "center",
161
+ gap: 4,
162
+ backgroundColor: _sharedUi.macOSColors.background.hover,
163
+ paddingHorizontal: 10,
164
+ paddingVertical: 5,
165
+ borderRadius: 12,
166
+ borderWidth: 1,
167
+ borderColor: _sharedUi.macOSColors.border.default
168
+ },
169
+ headerChipActive: {
170
+ backgroundColor: _sharedUi.macOSColors.semantic.infoBackground,
171
+ borderColor: _sharedUi.macOSColors.semantic.info + "50",
172
+ shadowColor: _sharedUi.macOSColors.semantic.info,
173
+ shadowOffset: {
174
+ width: 0,
175
+ height: 1
176
+ },
177
+ shadowOpacity: 0.08,
178
+ shadowRadius: 2,
179
+ elevation: 1
180
+ },
181
+ headerChipValue: {
182
+ fontSize: 12,
183
+ fontWeight: "600",
184
+ fontFamily: "monospace"
185
+ },
186
+ headerActionButton: {
187
+ width: 32,
188
+ height: 32,
189
+ borderRadius: 8,
190
+ backgroundColor: _sharedUi.macOSColors.background.hover,
191
+ borderWidth: 1,
192
+ borderColor: _sharedUi.macOSColors.border.default,
193
+ alignItems: "center",
194
+ justifyContent: "center"
195
+ },
196
+ headerActionButtonDisabled: {
197
+ opacity: 0.55
198
+ }
199
+ });
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkListScreen = NetworkListScreen;
7
+ var _reactNative = require("react-native");
8
+ var _sharedUi = require("@buoy-gg/shared-ui");
9
+ var _NetworkEventItemCompact = require("./NetworkEventItemCompact");
10
+ var _useNetworkEventList = require("../hooks/useNetworkEventList");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ /**
13
+ * The network tool's LIST screen — owns the event-store subscription, the
14
+ * FlatList, empty state, disabled banner, and the free-tier locked-history
15
+ * footer. Decomposed from NetworkModal per rule3 so per-event re-renders
16
+ * are confined to this subtree (plus the header's live leaves) instead of
17
+ * the whole modal.
18
+ *
19
+ * Stable-tree contract (rule4): hooks run unconditionally, then the
20
+ * component returns null when not visible. The subscription is gated with
21
+ * `paused || !visible` so a hidden list doesn't subscribe at all.
22
+ */
23
+
24
+ function EmptyState({
25
+ isEnabled
26
+ }) {
27
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
28
+ style: styles.emptyState,
29
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Globe, {
30
+ size: 32,
31
+ color: _sharedUi.macOSColors.text.muted
32
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
33
+ style: styles.emptyTitle,
34
+ children: "No network events"
35
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
36
+ style: styles.emptyText,
37
+ children: isEnabled ? "Network requests will appear here" : "Enable interception to start capturing"
38
+ })]
39
+ });
40
+ }
41
+ const keyExtractor = item => item.id;
42
+ function NetworkListScreen({
43
+ visible,
44
+ paused,
45
+ filter,
46
+ ignoredPatterns,
47
+ isPro,
48
+ searchText,
49
+ onEventPress,
50
+ onUpgradePress
51
+ }) {
52
+ const {
53
+ visibleEvents,
54
+ hasLockedEvents,
55
+ lockedEventCount
56
+ } = (0, _useNetworkEventList.useNetworkEventList)({
57
+ filter,
58
+ ignoredPatterns,
59
+ paused: paused || !visible,
60
+ isPro
61
+ });
62
+ if (!visible) return null;
63
+ const isEnabled = !paused;
64
+ const renderItem = ({
65
+ item
66
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkEventItemCompact.NetworkEventItemCompact, {
67
+ event: item,
68
+ onPress: onEventPress
69
+ });
70
+ const renderListFooter = () => {
71
+ if (!hasLockedEvents) return null;
72
+ const isSearching = searchText.length > 0;
73
+ const bannerText = isSearching ? `${lockedEventCount} matching ${lockedEventCount === 1 ? "request" : "requests"} in locked history` : `${lockedEventCount} requests locked`;
74
+ const subtextMessage = isSearching ? "Upgrade to Pro to search your full request history" : "Upgrade to Pro to unlock full request history";
75
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
76
+ style: styles.limitBanner,
77
+ onPress: onUpgradePress,
78
+ activeOpacity: 0.8,
79
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
80
+ style: styles.limitBannerContent,
81
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
82
+ style: styles.limitBannerLeft,
83
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
84
+ size: 16,
85
+ color: "#20C997"
86
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
87
+ style: styles.limitBannerText,
88
+ children: bannerText
89
+ })]
90
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProBadge, {})]
91
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
92
+ style: styles.limitBannerSubtext,
93
+ children: subtextMessage
94
+ })]
95
+ });
96
+ };
97
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
98
+ children: [!isEnabled ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
99
+ style: styles.disabledBanner,
100
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Power, {
101
+ size: 14,
102
+ color: _sharedUi.macOSColors.semantic.warning
103
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
104
+ style: styles.disabledText,
105
+ children: "Network interception is disabled"
106
+ })]
107
+ }) : null, visibleEvents.length > 0 || hasLockedEvents ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
108
+ data: visibleEvents,
109
+ renderItem: renderItem,
110
+ keyExtractor: keyExtractor,
111
+ contentContainerStyle: styles.listContent,
112
+ showsVerticalScrollIndicator: true,
113
+ removeClippedSubviews: true,
114
+ onEndReachedThreshold: 0.8
115
+ // The list OWNS scrolling (NetworkModal passes
116
+ // disableScrollWrapper while this screen is visible) so FlatList
117
+ // virtualization actually works. The previous shipped config —
118
+ // scrollEnabled=false inside JsModal's ScrollView — mounted ALL
119
+ // rows: at the store's 500-event cap that measured 17 JS FPS /
120
+ // 133% CPU / 1.4GB vs ~53 JS FPS / 52% / 360MB virtualized
121
+ // (buoy-optimize batch 2026-07-06, batch-1783396983529-6l7w).
122
+ // Window/batch values are the batch's "tuned" winner.
123
+ ,
124
+ scrollEnabled: true,
125
+ initialNumToRender: 12,
126
+ maxToRenderPerBatch: 8,
127
+ windowSize: 5,
128
+ updateCellsBatchingPeriod: 50,
129
+ "sentry-label": "ignore network events list",
130
+ ListFooterComponent: renderListFooter
131
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {
132
+ isEnabled: isEnabled
133
+ })]
134
+ });
135
+ }
136
+ const styles = _reactNative.StyleSheet.create({
137
+ disabledBanner: {
138
+ flexDirection: "row",
139
+ alignItems: "center",
140
+ gap: 8,
141
+ padding: 10,
142
+ marginHorizontal: 12,
143
+ marginTop: 8,
144
+ backgroundColor: _sharedUi.macOSColors.semantic.warningBackground,
145
+ borderRadius: 8,
146
+ borderWidth: 1,
147
+ borderColor: _sharedUi.macOSColors.semantic.warning + "20"
148
+ },
149
+ disabledText: {
150
+ color: _sharedUi.macOSColors.semantic.warning,
151
+ fontSize: 11,
152
+ flex: 1
153
+ },
154
+ listContent: {
155
+ paddingTop: 8
156
+ },
157
+ emptyState: {
158
+ alignItems: "center",
159
+ paddingVertical: 40
160
+ },
161
+ emptyTitle: {
162
+ color: _sharedUi.macOSColors.text.primary,
163
+ fontSize: 14,
164
+ fontWeight: "600",
165
+ marginTop: 12,
166
+ marginBottom: 6
167
+ },
168
+ emptyText: {
169
+ color: _sharedUi.macOSColors.text.muted,
170
+ fontSize: 12,
171
+ textAlign: "center"
172
+ },
173
+ limitBanner: {
174
+ backgroundColor: "#1A1A1A",
175
+ borderRadius: 10,
176
+ borderWidth: 1,
177
+ borderColor: "#20C997" + "40",
178
+ padding: 12,
179
+ marginHorizontal: 12,
180
+ marginTop: 12,
181
+ marginBottom: 34
182
+ },
183
+ limitBannerContent: {
184
+ flexDirection: "row",
185
+ alignItems: "center",
186
+ justifyContent: "space-between",
187
+ marginBottom: 4
188
+ },
189
+ limitBannerLeft: {
190
+ flexDirection: "row",
191
+ alignItems: "center",
192
+ gap: 8
193
+ },
194
+ limitBannerText: {
195
+ color: "#E0E0E0",
196
+ fontSize: 13,
197
+ fontWeight: "600"
198
+ },
199
+ limitBannerSubtext: {
200
+ color: "#888888",
201
+ fontSize: 11
202
+ }
203
+ });