@buoy-gg/network 5.0.0 → 6.0.1

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 (41) hide show
  1. package/README.md +29 -353
  2. package/lib/commonjs/index.js +14 -0
  3. package/lib/commonjs/network/components/NetworkEventItemCompact.js +32 -18
  4. package/lib/commonjs/network/components/NetworkHeaderLive.js +199 -0
  5. package/lib/commonjs/network/components/NetworkListScreen.js +203 -0
  6. package/lib/commonjs/network/components/NetworkModal.js +153 -630
  7. package/lib/commonjs/network/hooks/useNetworkBadgeCounts.js +83 -0
  8. package/lib/commonjs/network/hooks/useNetworkEventList.js +93 -0
  9. package/lib/commonjs/network/hooks/useNetworkHasMatches.js +56 -0
  10. package/lib/commonjs/network/utils/filterNetworkEvents.js +96 -0
  11. package/lib/commonjs/network/utils/generateNetworkCopyText.js +139 -0
  12. package/lib/module/index.js +5 -0
  13. package/lib/module/network/components/NetworkEventItemCompact.js +32 -18
  14. package/lib/module/network/components/NetworkHeaderLive.js +193 -0
  15. package/lib/module/network/components/NetworkListScreen.js +199 -0
  16. package/lib/module/network/components/NetworkModal.js +157 -634
  17. package/lib/module/network/hooks/useNetworkBadgeCounts.js +79 -0
  18. package/lib/module/network/hooks/useNetworkEventList.js +89 -0
  19. package/lib/module/network/hooks/useNetworkHasMatches.js +52 -0
  20. package/lib/module/network/utils/filterNetworkEvents.js +92 -0
  21. package/lib/module/network/utils/generateNetworkCopyText.js +135 -0
  22. package/lib/typescript/index.d.ts +2 -0
  23. package/lib/typescript/index.d.ts.map +1 -1
  24. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +0 -4
  25. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
  26. package/lib/typescript/network/components/NetworkHeaderLive.d.ts +52 -0
  27. package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -0
  28. package/lib/typescript/network/components/NetworkListScreen.d.ts +27 -0
  29. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -0
  30. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  31. package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts +24 -0
  32. package/lib/typescript/network/hooks/useNetworkBadgeCounts.d.ts.map +1 -0
  33. package/lib/typescript/network/hooks/useNetworkEventList.d.ts +34 -0
  34. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -0
  35. package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts +23 -0
  36. package/lib/typescript/network/hooks/useNetworkHasMatches.d.ts.map +1 -0
  37. package/lib/typescript/network/utils/filterNetworkEvents.d.ts +13 -0
  38. package/lib/typescript/network/utils/filterNetworkEvents.d.ts.map +1 -0
  39. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +11 -0
  40. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -0
  41. package/package.json +3 -3
@@ -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
+ });