@buoy-gg/network 7.0.15 → 7.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/lib/commonjs/index.web.js +395 -0
  2. package/lib/commonjs/network/components/DataViewer.web.js +879 -0
  3. package/lib/commonjs/network/components/NetworkCopyScreen.web.js +48 -0
  4. package/lib/commonjs/network/components/NetworkCopySettingsView.web.js +1242 -0
  5. package/lib/commonjs/network/components/NetworkDetailScreen.web.js +101 -0
  6. package/lib/commonjs/network/components/NetworkDetailStepper.web.js +296 -0
  7. package/lib/commonjs/network/components/NetworkEventDetailView.web.js +1498 -0
  8. package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +802 -0
  9. package/lib/commonjs/network/components/NetworkFilterScreen.web.js +55 -0
  10. package/lib/commonjs/network/components/NetworkFilterViewV3.web.js +1310 -0
  11. package/lib/commonjs/network/components/NetworkHeaderLive.web.js +604 -0
  12. package/lib/commonjs/network/components/NetworkListScreen.js +2 -1
  13. package/lib/commonjs/network/components/NetworkListScreen.web.js +607 -0
  14. package/lib/commonjs/network/components/NetworkModal.js +12 -3
  15. package/lib/commonjs/network/components/NetworkNumberPrompt.web.js +232 -0
  16. package/lib/commonjs/network/components/NetworkOverrideEditor.web.js +942 -0
  17. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.web.js +133 -0
  18. package/lib/commonjs/network/components/NetworkOverridesButton.web.js +143 -0
  19. package/lib/commonjs/network/components/NetworkOverridesScreen.js +160 -126
  20. package/lib/commonjs/network/components/NetworkOverridesScreen.web.js +830 -0
  21. package/lib/commonjs/network/components/NetworkSavedScreen.web.js +459 -0
  22. package/lib/commonjs/network/components/response-editor/Explorer.web.js +1136 -0
  23. package/lib/commonjs/network/hooks/useNetworkEventList.js +12 -2
  24. package/lib/commonjs/network/overrides/applyToXhr.js +276 -15
  25. package/lib/commonjs/network/utils/generateNetworkCopyText.js +32 -5
  26. package/lib/commonjs/network/utils/networkEventStore.js +12 -4
  27. package/lib/commonjs/network/utils/networkListener.js +1 -16
  28. package/lib/module/index.web.js +81 -0
  29. package/lib/module/network/components/DataViewer.web.js +875 -0
  30. package/lib/module/network/components/NetworkCopyScreen.web.js +44 -0
  31. package/lib/module/network/components/NetworkCopySettingsView.web.js +1237 -0
  32. package/lib/module/network/components/NetworkDetailScreen.web.js +97 -0
  33. package/lib/module/network/components/NetworkDetailStepper.web.js +294 -0
  34. package/lib/module/network/components/NetworkEventDetailView.web.js +1496 -0
  35. package/lib/module/network/components/NetworkEventItemCompact.web.js +800 -0
  36. package/lib/module/network/components/NetworkFilterScreen.web.js +51 -0
  37. package/lib/module/network/components/NetworkFilterViewV3.web.js +1306 -0
  38. package/lib/module/network/components/NetworkHeaderLive.web.js +598 -0
  39. package/lib/module/network/components/NetworkListScreen.js +2 -1
  40. package/lib/module/network/components/NetworkListScreen.web.js +605 -0
  41. package/lib/module/network/components/NetworkModal.js +12 -3
  42. package/lib/module/network/components/NetworkNumberPrompt.web.js +230 -0
  43. package/lib/module/network/components/NetworkOverrideEditor.web.js +939 -0
  44. package/lib/module/network/components/NetworkOverrideHeaderButton.web.js +131 -0
  45. package/lib/module/network/components/NetworkOverridesButton.web.js +141 -0
  46. package/lib/module/network/components/NetworkOverridesScreen.js +161 -127
  47. package/lib/module/network/components/NetworkOverridesScreen.web.js +826 -0
  48. package/lib/module/network/components/NetworkSavedScreen.web.js +457 -0
  49. package/lib/module/network/components/response-editor/Explorer.web.js +1133 -0
  50. package/lib/module/network/hooks/useNetworkEventList.js +12 -2
  51. package/lib/module/network/overrides/applyToXhr.js +275 -15
  52. package/lib/module/network/utils/generateNetworkCopyText.js +28 -1
  53. package/lib/module/network/utils/networkEventStore.js +12 -4
  54. package/lib/module/network/utils/networkListener.js +1 -16
  55. package/lib/typescript/index.web.d.ts +56 -0
  56. package/lib/typescript/index.web.d.ts.map +1 -0
  57. package/lib/typescript/network/components/DataViewer.web.d.ts +78 -0
  58. package/lib/typescript/network/components/DataViewer.web.d.ts.map +1 -0
  59. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts +27 -0
  60. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts.map +1 -0
  61. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts +68 -0
  62. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts.map +1 -0
  63. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts +45 -0
  64. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts.map +1 -0
  65. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts +40 -0
  66. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts.map +1 -0
  67. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts +67 -0
  68. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts.map +1 -0
  69. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts +58 -0
  70. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +1 -0
  71. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts +28 -0
  72. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts.map +1 -0
  73. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts +74 -0
  74. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts.map +1 -0
  75. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts +112 -0
  76. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +1 -0
  77. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
  78. package/lib/typescript/network/components/NetworkListScreen.web.d.ts +63 -0
  79. package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +1 -0
  80. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  81. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts +38 -0
  82. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts.map +1 -0
  83. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts +43 -0
  84. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts.map +1 -0
  85. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts +32 -0
  86. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts.map +1 -0
  87. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts +26 -0
  88. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts.map +1 -0
  89. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts +9 -0
  90. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -1
  91. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts +69 -0
  92. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts.map +1 -0
  93. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts +67 -0
  94. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts.map +1 -0
  95. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts +56 -0
  96. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts.map +1 -0
  97. package/lib/typescript/network/hooks/useNetworkEventList.d.ts +10 -0
  98. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -1
  99. package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts.map +1 -1
  100. package/lib/typescript/network/overrides/applyToXhr.d.ts +40 -14
  101. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -1
  102. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +34 -1
  103. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -1
  104. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  105. package/lib/typescript/network/utils/networkListener.d.ts +0 -1
  106. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  107. package/package.json +2 -2
@@ -0,0 +1,605 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * NetworkListScreen.web — DOM twin of NetworkListScreen.tsx.
5
+ *
6
+ * The network tool's LIST screen: owns the event-store subscription, the
7
+ * scrolling list, the OVERRIDDEN and PINNED strips, empty state, disabled
8
+ * banner, and the free-tier locked-history footer. Identical prop contract,
9
+ * hook usage and derivation order as the native file — the data layer is
10
+ * REUSED, not forked (`useNetworkEventList`, `useNetworkSaved`,
11
+ * `useOverrideRules`, `ruleMatches`, `splitPinned` are all platform-free).
12
+ *
13
+ * Stable-tree contract (rule4): hooks run unconditionally, then the
14
+ * component returns null when not visible. The subscription is gated with
15
+ * `paused || !visible` so a hidden list doesn't subscribe at all.
16
+ *
17
+ * shared-ui RN chrome is replicated as DOM with provenance (web consumers
18
+ * resolve `@buoy-gg/shared-ui` to a minimal shim):
19
+ * - `macOSColors` / `buoyColors` — inlined verbatim subsets from
20
+ * shared/src/ui/gameUI/constants/.
21
+ * - Icons — inline lucide SVGs (shared-ui's icons are lucide re-exports).
22
+ * - `NetworkUpgradeBanner` (which itself leans on shared-ui `ProBadge` +
23
+ * `Zap`) — inlined DOM twin below, values from
24
+ * NetworkUpgradeBanner.tsx + license/FeatureGate.tsx `proBadge` styles.
25
+ *
26
+ * Intentional deviations (all logged):
27
+ * - FlatList virtualization → a plain scrolled div. The store caps at 500
28
+ * events and web rows are cheap DOM; the window/batch tuning the native
29
+ * list carries is a FlatList-only concern.
30
+ * (`extraData={savedLiveIds}` is likewise moot — plain `map` has no
31
+ * PureComponent bail-out to bust.)
32
+ * - The upgrade banner's `ProBadge` renders the static gold "PRO" — the
33
+ * Weekend Pass variant reads `useProAccess` from @buoy-gg/license, which
34
+ * is not part of the web port.
35
+ * - `onEventPress` opens the detail view (ported in F3 as
36
+ * NetworkEventDetailView.web / NetworkDetailScreen.web) — callers wire the
37
+ * prop to `networkSelectionStore.select`, the screen just calls it
38
+ * (native behavior; nothing stubbed here).
39
+ * - Native's row tap opens the detail view and long-press toggles the pin;
40
+ * both are wired identically here (long-press = 500ms pointer hold, see
41
+ * NetworkEventItemCompact.web).
42
+ *
43
+ * F4 integration point: `ignoredPatterns` is a prop (same as native). The
44
+ * native root feeds it from shared-ui's persisted `useIgnoredPatterns` store,
45
+ * which the web shim does not expose — web hosts pass `[]` until the filter
46
+ * feature lands.
47
+ *
48
+ * @platform React DOM Web
49
+ */
50
+
51
+ import { useCallback } from "react";
52
+ import { NetworkEventItemCompact } from "./NetworkEventItemCompact.web";
53
+ import { useNetworkEventList } from "../hooks/useNetworkEventList";
54
+ import { useNetworkSaved } from "../hooks/useNetworkSaved";
55
+ import { useOverrideRules } from "../hooks/useOverrideRules";
56
+ import { ruleMatches } from "../overrides/matchRule";
57
+ import { splitPinned } from "../utils/withPinnedFirst";
58
+
59
+ // =============================
60
+ // Native palettes (provenance)
61
+ // =============================
62
+
63
+ /** macOSColors subset — shared/src/ui/gameUI/constants/macOSDesignSystemColors.ts, verbatim. */
64
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
65
+ const macOSColors = {
66
+ background: {
67
+ card: "#1A1A1C"
68
+ },
69
+ border: {
70
+ default: "#2D2D2F"
71
+ },
72
+ text: {
73
+ primary: "#F5F5F7",
74
+ muted: "#8E8E93"
75
+ },
76
+ semantic: {
77
+ warning: "#FFEB3B",
78
+ warningBackground: "rgba(255, 235, 59, 0.15)",
79
+ info: "#00B8E6"
80
+ }
81
+ };
82
+
83
+ /** buoyColors subset — shared/src/ui/gameUI/constants/gameUIColors.ts (macOS theme), verbatim. */
84
+ const buoy = {
85
+ card: "#1A1A1A",
86
+ text: "#E0E0E0",
87
+ textMuted: "#888888",
88
+ primary: "#20C997"
89
+ };
90
+
91
+ // =============================
92
+ // Inline lucide icons (shared-ui icons are lucide re-exports)
93
+ // =============================
94
+
95
+ function Svg({
96
+ size = 14,
97
+ color = "currentColor",
98
+ children
99
+ }) {
100
+ return /*#__PURE__*/_jsx("svg", {
101
+ width: size,
102
+ height: size,
103
+ viewBox: "0 0 24 24",
104
+ fill: "none",
105
+ stroke: color,
106
+ strokeWidth: 2,
107
+ strokeLinecap: "round",
108
+ strokeLinejoin: "round",
109
+ style: {
110
+ flexShrink: 0,
111
+ display: "block"
112
+ },
113
+ children: children
114
+ });
115
+ }
116
+ const Globe = p => /*#__PURE__*/_jsxs(Svg, {
117
+ ...p,
118
+ children: [/*#__PURE__*/_jsx("circle", {
119
+ cx: "12",
120
+ cy: "12",
121
+ r: "10"
122
+ }), /*#__PURE__*/_jsx("path", {
123
+ d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"
124
+ }), /*#__PURE__*/_jsx("path", {
125
+ d: "M2 12h20"
126
+ })]
127
+ });
128
+ const Pin = p => /*#__PURE__*/_jsxs(Svg, {
129
+ ...p,
130
+ children: [/*#__PURE__*/_jsx("path", {
131
+ d: "M12 17v5"
132
+ }), /*#__PURE__*/_jsx("path", {
133
+ d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1z"
134
+ })]
135
+ });
136
+ const Power = p => /*#__PURE__*/_jsxs(Svg, {
137
+ ...p,
138
+ children: [/*#__PURE__*/_jsx("path", {
139
+ d: "M12 2v10"
140
+ }), /*#__PURE__*/_jsx("path", {
141
+ d: "M18.4 6.6a9 9 0 1 1-12.77.04"
142
+ })]
143
+ });
144
+ const FlaskConical = p => /*#__PURE__*/_jsxs(Svg, {
145
+ ...p,
146
+ children: [/*#__PURE__*/_jsx("path", {
147
+ d: "M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.55a1 1 0 0 0 .9 1.45h12.76a1 1 0 0 0 .9-1.45l-5.069-10.127A2 2 0 0 1 14 9.527V2"
148
+ }), /*#__PURE__*/_jsx("path", {
149
+ d: "M8.5 2h7"
150
+ }), /*#__PURE__*/_jsx("path", {
151
+ d: "M7 16h10"
152
+ })]
153
+ });
154
+ const Zap = p => /*#__PURE__*/_jsx(Svg, {
155
+ ...p,
156
+ children: /*#__PURE__*/_jsx("polygon", {
157
+ points: "13 2 3 14 12 14 11 22 21 10 12 10 13 2"
158
+ })
159
+ });
160
+
161
+ // =============================
162
+ // NetworkUpgradeBanner DOM twin (provenance: NetworkUpgradeBanner.tsx +
163
+ // license/FeatureGate.tsx proBadge styles; ProBadge is static "PRO" — see
164
+ // the deviations log)
165
+ // =============================
166
+
167
+ function NetworkUpgradeBannerWeb({
168
+ title,
169
+ subtitle,
170
+ onPress
171
+ }) {
172
+ return /*#__PURE__*/_jsxs("button", {
173
+ type: "button",
174
+ style: styles.upgradeBanner,
175
+ onClick: onPress,
176
+ children: [/*#__PURE__*/_jsxs("span", {
177
+ style: styles.upgradeContent,
178
+ children: [/*#__PURE__*/_jsxs("span", {
179
+ style: styles.upgradeLeft,
180
+ children: [/*#__PURE__*/_jsx(Zap, {
181
+ size: 16,
182
+ color: buoy.primary
183
+ }), /*#__PURE__*/_jsx("span", {
184
+ style: styles.upgradeTitle,
185
+ children: title
186
+ })]
187
+ }), /*#__PURE__*/_jsx("span", {
188
+ style: styles.proBadge,
189
+ children: "PRO"
190
+ })]
191
+ }), /*#__PURE__*/_jsx("span", {
192
+ style: styles.upgradeSubtitle,
193
+ children: subtitle
194
+ })]
195
+ });
196
+ }
197
+
198
+ // =============================
199
+ // Screen (structure mirrors the native file 1:1)
200
+ // =============================
201
+
202
+ function EmptyState({
203
+ isEnabled
204
+ }) {
205
+ return /*#__PURE__*/_jsxs("div", {
206
+ style: styles.emptyState,
207
+ children: [/*#__PURE__*/_jsx(Globe, {
208
+ size: 32,
209
+ color: macOSColors.text.muted
210
+ }), /*#__PURE__*/_jsx("div", {
211
+ style: styles.emptyTitle,
212
+ children: "No network events"
213
+ }), /*#__PURE__*/_jsx("div", {
214
+ style: styles.emptyText,
215
+ children: isEnabled ? "Network requests will appear here" : "Enable interception to start capturing"
216
+ })]
217
+ });
218
+ }
219
+
220
+ /**
221
+ * The PINNED strip above the live list. Renders OUTSIDE the scrolled list on
222
+ * purpose — pins ignore the filters, are bounded at MAX_PINNED in the store,
223
+ * and keeping them out of the list keeps the boundary from moving as events
224
+ * arrive (same rationale as native).
225
+ */
226
+ function PinnedSection({
227
+ events,
228
+ onEventPress,
229
+ onLongPress,
230
+ savedLiveIds,
231
+ onClearPinned
232
+ }) {
233
+ if (events.length === 0) return null;
234
+ return /*#__PURE__*/_jsxs("div", {
235
+ style: styles.pinnedSection,
236
+ children: [/*#__PURE__*/_jsxs("div", {
237
+ style: styles.pinnedHeader,
238
+ children: [/*#__PURE__*/_jsx(Pin, {
239
+ size: 11,
240
+ color: macOSColors.semantic.info
241
+ }), /*#__PURE__*/_jsxs("span", {
242
+ style: styles.pinnedTitle,
243
+ children: ["PINNED \xB7 ", events.length]
244
+ }), /*#__PURE__*/_jsx("button", {
245
+ type: "button",
246
+ onClick: onClearPinned,
247
+ style: styles.pinnedClear,
248
+ children: /*#__PURE__*/_jsx("span", {
249
+ style: styles.pinnedClearText,
250
+ children: "Unpin all"
251
+ })
252
+ })]
253
+ }), events.map(event => /*#__PURE__*/_jsx(NetworkEventItemCompact, {
254
+ event: event,
255
+ onPress: onEventPress,
256
+ onLongPress: onLongPress,
257
+ pinned: true,
258
+ saved: savedLiveIds.has(event.id)
259
+ }, event.id))]
260
+ });
261
+ }
262
+
263
+ /**
264
+ * The OVERRIDDEN strip, above the pins. Rows are matched against the RULES
265
+ * rather than filtered on the `override` mark, and the header button is
266
+ * PAUSE, not "Manage" — same reasoning as native (see NetworkListScreen.tsx).
267
+ */
268
+ function OverriddenSection({
269
+ events,
270
+ onEventPress,
271
+ onLongPress,
272
+ savedLiveIds,
273
+ paused,
274
+ onTogglePaused
275
+ }) {
276
+ if (events.length === 0) return null;
277
+ return /*#__PURE__*/_jsxs("div", {
278
+ style: {
279
+ ...styles.overriddenSection,
280
+ ...(paused ? styles.overriddenSectionOff : null)
281
+ },
282
+ children: [/*#__PURE__*/_jsxs("div", {
283
+ style: styles.overriddenHeader,
284
+ children: [/*#__PURE__*/_jsx(FlaskConical, {
285
+ size: 11,
286
+ color: paused ? macOSColors.text.muted : macOSColors.semantic.warning
287
+ }), /*#__PURE__*/_jsx("span", {
288
+ style: {
289
+ ...styles.overriddenTitle,
290
+ ...(paused ? styles.overriddenTitleOff : null)
291
+ },
292
+ children: paused ? `PAUSED · ${events.length}` : `OVERRIDDEN · ${events.length}`
293
+ }), /*#__PURE__*/_jsx("button", {
294
+ type: "button",
295
+ onClick: onTogglePaused,
296
+ style: styles.overriddenManage,
297
+ children: /*#__PURE__*/_jsx("span", {
298
+ style: {
299
+ ...styles.overriddenManageText,
300
+ ...(paused ? styles.overriddenTitleOff : null)
301
+ },
302
+ children: paused ? "Resume" : "Pause"
303
+ })
304
+ })]
305
+ }), events.map(event => /*#__PURE__*/_jsx(NetworkEventItemCompact, {
306
+ event: event,
307
+ onPress: onEventPress,
308
+ onLongPress: onLongPress,
309
+ overridden: true,
310
+ saved: savedLiveIds.has(event.id)
311
+ }, event.id))]
312
+ });
313
+ }
314
+ export function NetworkListScreen({
315
+ visible,
316
+ paused,
317
+ filter,
318
+ ignoredPatterns,
319
+ isPro,
320
+ searchText,
321
+ onEventPress,
322
+ onUpgradePress
323
+ }) {
324
+ const {
325
+ visibleEvents,
326
+ hasLockedEvents,
327
+ lockedEventCount
328
+ } = useNetworkEventList({
329
+ filter,
330
+ ignoredPatterns,
331
+ paused: paused || !visible,
332
+ isPro
333
+ });
334
+
335
+ // Pins come from the persisted saved store, NOT from the live list — that is
336
+ // what lets a pinned failure survive Clear, the 500-event cap and a reload.
337
+ const {
338
+ pinnedEvents,
339
+ pinnedLiveIds,
340
+ savedLiveIds,
341
+ togglePin,
342
+ clearPinned
343
+ } = useNetworkSaved();
344
+
345
+ // Which rules are live right now — the strip below reflects the rules, not
346
+ // the history, so it's correct the moment a rule is made or removed.
347
+ const {
348
+ rules,
349
+ enabled: overridesEnabled,
350
+ setEnabled,
351
+ isRuleSpent
352
+ } = useOverrideRules();
353
+
354
+ // Stable identity: handed to memoized rows.
355
+ const handleLongPress = useCallback(event => {
356
+ togglePin(event);
357
+ }, [togglePin]);
358
+
359
+ // Everything above is a hook and must run unconditionally; everything below
360
+ // is work (same rule4 contract as native).
361
+ if (!visible) return null;
362
+ const {
363
+ pinned,
364
+ rest
365
+ } = splitPinned(visibleEvents, pinnedEvents, pinnedLiveIds);
366
+
367
+ // Hoist anything an active rule covers. Done after `splitPinned` so a request
368
+ // that is both pinned and overridden appears once, in the louder section.
369
+ const activeRules = rules.filter(rule => rule.enabled && !isRuleSpent(rule));
370
+ const isOverridden = event => activeRules.some(rule => ruleMatches(rule, event.url, String(event.method || "GET")));
371
+ const overridden = activeRules.length ? [...pinned, ...rest].filter(isOverridden) : [];
372
+ const overriddenIds = new Set(overridden.map(event => event.id));
373
+ const pinnedRows = pinned.filter(event => !overriddenIds.has(event.id));
374
+ const restRows = overriddenIds.size ? rest.filter(event => !overriddenIds.has(event.id)) : rest;
375
+ const isEnabled = !paused;
376
+ const renderListFooter = () => {
377
+ if (!hasLockedEvents) return null;
378
+ const isSearching = searchText.length > 0;
379
+ const bannerText = isSearching ? `${lockedEventCount} matching ${lockedEventCount === 1 ? "request" : "requests"} in locked history` : `${lockedEventCount} requests locked`;
380
+ const subtextMessage = isSearching ? "Upgrade to Pro to search your full request history" : "Upgrade to Pro to unlock full request history";
381
+ return /*#__PURE__*/_jsx(NetworkUpgradeBannerWeb, {
382
+ title: bannerText,
383
+ subtitle: subtextMessage,
384
+ onPress: onUpgradePress
385
+ });
386
+ };
387
+ return /*#__PURE__*/_jsxs("div", {
388
+ style: styles.root,
389
+ children: [!isEnabled ? /*#__PURE__*/_jsxs("div", {
390
+ style: styles.disabledBanner,
391
+ children: [/*#__PURE__*/_jsx(Power, {
392
+ size: 14,
393
+ color: macOSColors.semantic.warning
394
+ }), /*#__PURE__*/_jsx("span", {
395
+ style: styles.disabledText,
396
+ children: "Network interception is disabled"
397
+ })]
398
+ }) : null, /*#__PURE__*/_jsx(OverriddenSection, {
399
+ events: overridden,
400
+ onEventPress: onEventPress,
401
+ onLongPress: handleLongPress,
402
+ savedLiveIds: savedLiveIds,
403
+ paused: !overridesEnabled,
404
+ onTogglePaused: () => setEnabled(!overridesEnabled)
405
+ }), /*#__PURE__*/_jsx(PinnedSection, {
406
+ events: pinnedRows,
407
+ onEventPress: onEventPress,
408
+ onLongPress: handleLongPress,
409
+ savedLiveIds: savedLiveIds,
410
+ onClearPinned: clearPinned
411
+ }), restRows.length > 0 || hasLockedEvents ?
412
+ /*#__PURE__*/
413
+ // DEVIATION: FlatList → plain scrolled div (see file header). The
414
+ // list still OWNS scrolling — hosts render this screen without a
415
+ // scroll wrapper (JsModal's `disableScrollWrapper`), same as native.
416
+ _jsx("div", {
417
+ style: styles.list,
418
+ "aria-label": "Network events list",
419
+ children: /*#__PURE__*/_jsxs("div", {
420
+ style: styles.listContent,
421
+ children: [restRows.map(item => /*#__PURE__*/_jsx(NetworkEventItemCompact, {
422
+ event: item,
423
+ onPress: onEventPress,
424
+ onLongPress: handleLongPress,
425
+ saved: savedLiveIds.has(item.id)
426
+ }, item.id)), renderListFooter()]
427
+ })
428
+ }) : pinnedRows.length > 0 || overridden.length > 0 ? null : /*#__PURE__*/_jsx(EmptyState, {
429
+ isEnabled: isEnabled
430
+ })]
431
+ });
432
+ }
433
+
434
+ // Values mirror the native StyleSheet block 1:1.
435
+ const styles = {
436
+ root: {
437
+ display: "flex",
438
+ flexDirection: "column",
439
+ flex: 1,
440
+ minHeight: 0
441
+ },
442
+ /**
443
+ * The strip is separated from the live list by a real rule, not just space:
444
+ * these rows do NOT obey the filters, and the boundary is what tells the
445
+ * user why a request they just filtered out is still on screen.
446
+ */
447
+ overriddenSection: {
448
+ borderBottom: `1px solid ${macOSColors.semantic.warning}44`,
449
+ backgroundColor: macOSColors.semantic.warningBackground
450
+ },
451
+ overriddenSectionOff: {
452
+ borderBottom: `1px solid ${macOSColors.border.default}`,
453
+ backgroundColor: macOSColors.background.card
454
+ },
455
+ overriddenTitleOff: {
456
+ color: macOSColors.text.muted
457
+ },
458
+ overriddenHeader: {
459
+ display: "flex",
460
+ flexDirection: "row",
461
+ alignItems: "center",
462
+ gap: 6,
463
+ padding: "8px 12px 4px"
464
+ },
465
+ overriddenTitle: {
466
+ flex: 1,
467
+ fontSize: 10,
468
+ fontWeight: 700,
469
+ letterSpacing: 0.8,
470
+ color: macOSColors.semantic.warning
471
+ },
472
+ overriddenManage: {
473
+ padding: "2px 6px",
474
+ background: "none",
475
+ border: "none",
476
+ cursor: "pointer",
477
+ margin: 0
478
+ },
479
+ overriddenManageText: {
480
+ fontSize: 10,
481
+ fontWeight: 600,
482
+ color: macOSColors.semantic.warning
483
+ },
484
+ pinnedSection: {
485
+ borderBottom: `1px solid ${macOSColors.border.default}`,
486
+ paddingBottom: 4
487
+ },
488
+ pinnedHeader: {
489
+ display: "flex",
490
+ flexDirection: "row",
491
+ alignItems: "center",
492
+ gap: 6,
493
+ padding: "10px 20px 6px"
494
+ },
495
+ pinnedTitle: {
496
+ flex: 1,
497
+ color: macOSColors.semantic.info,
498
+ fontSize: 10,
499
+ fontWeight: 700,
500
+ letterSpacing: 0.6,
501
+ fontFamily: "ui-monospace, Menlo, monospace"
502
+ },
503
+ pinnedClear: {
504
+ padding: "2px 6px",
505
+ background: "none",
506
+ border: "none",
507
+ cursor: "pointer",
508
+ margin: 0
509
+ },
510
+ pinnedClearText: {
511
+ color: macOSColors.text.muted,
512
+ fontSize: 10
513
+ },
514
+ disabledBanner: {
515
+ display: "flex",
516
+ flexDirection: "row",
517
+ alignItems: "center",
518
+ gap: 8,
519
+ padding: 10,
520
+ margin: "8px 12px 0",
521
+ backgroundColor: macOSColors.semantic.warningBackground,
522
+ borderRadius: 8,
523
+ border: `1px solid ${macOSColors.semantic.warning}20`
524
+ },
525
+ disabledText: {
526
+ color: macOSColors.semantic.warning,
527
+ fontSize: 11,
528
+ flex: 1
529
+ },
530
+ list: {
531
+ flex: 1,
532
+ minHeight: 0,
533
+ overflowY: "auto"
534
+ },
535
+ listContent: {
536
+ paddingTop: 8
537
+ },
538
+ emptyState: {
539
+ display: "flex",
540
+ flexDirection: "column",
541
+ alignItems: "center",
542
+ padding: "40px 0"
543
+ },
544
+ emptyTitle: {
545
+ color: macOSColors.text.primary,
546
+ fontSize: 14,
547
+ fontWeight: 600,
548
+ marginTop: 12,
549
+ marginBottom: 6
550
+ },
551
+ emptyText: {
552
+ color: macOSColors.text.muted,
553
+ fontSize: 12,
554
+ textAlign: "center"
555
+ },
556
+ // NetworkUpgradeBanner.tsx values (buoyColors), verbatim.
557
+ upgradeBanner: {
558
+ display: "block",
559
+ width: "calc(100% - 24px)",
560
+ boxSizing: "border-box",
561
+ backgroundColor: buoy.card,
562
+ borderRadius: 10,
563
+ border: `1px solid ${buoy.primary}40`,
564
+ padding: 12,
565
+ margin: "12px 12px 34px",
566
+ textAlign: "left",
567
+ cursor: "pointer",
568
+ fontFamily: "inherit"
569
+ },
570
+ upgradeContent: {
571
+ display: "flex",
572
+ flexDirection: "row",
573
+ alignItems: "center",
574
+ justifyContent: "space-between",
575
+ marginBottom: 4
576
+ },
577
+ upgradeLeft: {
578
+ display: "inline-flex",
579
+ flexDirection: "row",
580
+ alignItems: "center",
581
+ gap: 8
582
+ },
583
+ upgradeTitle: {
584
+ color: buoy.text,
585
+ fontSize: 13,
586
+ fontWeight: 600
587
+ },
588
+ upgradeSubtitle: {
589
+ display: "block",
590
+ color: buoy.textMuted,
591
+ fontSize: 11
592
+ },
593
+ // license/FeatureGate.tsx `proBadge`/`proBadgeText`, verbatim.
594
+ proBadge: {
595
+ display: "inline-flex",
596
+ backgroundColor: `${buoy.primary}15`,
597
+ borderRadius: 4,
598
+ border: `1px solid ${buoy.primary}40`,
599
+ padding: "2px 6px",
600
+ fontSize: 10,
601
+ fontWeight: 700,
602
+ color: buoy.primary,
603
+ letterSpacing: 0.5
604
+ }
605
+ };
@@ -120,7 +120,8 @@ function NetworkFilterScreen({
120
120
  } = useNetworkEventList({
121
121
  filter,
122
122
  ignoredPatterns,
123
- paused: paused || !visible,
123
+ paused,
124
+ visible,
124
125
  isPro
125
126
  });
126
127
  if (!visible) return null;
@@ -148,7 +149,8 @@ function NetworkCopyScreen({
148
149
  } = useNetworkEventList({
149
150
  filter,
150
151
  ignoredPatterns,
151
- paused: paused || !visible,
152
+ paused,
153
+ visible,
152
154
  isPro
153
155
  });
154
156
  if (!visible) return null;
@@ -322,6 +324,11 @@ function NetworkModalInner({
322
324
  // Stable identity: the Overrides screen adopts the draft in an effect keyed
323
325
  // on both, so an unstable callback would re-run it every render.
324
326
  const handlePendingOverrideConsumed = useCallback(() => setPendingOverrideDraft(null), []);
327
+
328
+ // Non-null while the Overrides screen has its rule editor open — the editor
329
+ // is that screen's own state, so the header asks it first before popping the
330
+ // whole screen. See the prop's comment on NetworkOverridesScreen.
331
+ const overridesBackRef = useRef(null);
325
332
  const handleSearch = text => {
326
333
  setSearchText(text);
327
334
  setFilter(prev => ({
@@ -401,6 +408,7 @@ function NetworkModalInner({
401
408
  return /*#__PURE__*/_jsxs(ModalHeader, {
402
409
  children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
403
410
  onBack: () => {
411
+ if (overridesBackRef.current?.()) return;
404
412
  setShowOverridesView(false);
405
413
  setPendingOverrideDraft(null);
406
414
  }
@@ -551,7 +559,8 @@ function NetworkModalInner({
551
559
  isPro: isPro,
552
560
  onUpgradePress: () => showUpgrade("overrides"),
553
561
  pendingDraft: pendingOverrideDraft,
554
- onPendingConsumed: handlePendingOverrideConsumed
562
+ onPendingConsumed: handlePendingOverrideConsumed,
563
+ backHandlerRef: overridesBackRef
555
564
  // Inline (desktop panel) has no home indicator to clear.
556
565
  ,
557
566
  applySafeAreaInset: !inline && modalMode === "bottomSheet"