@buoy-gg/network 7.0.25 → 7.0.27

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 (49) hide show
  1. package/lib/commonjs/network/components/NetworkDetailStepper.js +2 -1
  2. package/lib/commonjs/network/components/NetworkEventDetailView.js +74 -72
  3. package/lib/commonjs/network/components/NetworkEventItemCompact.js +24 -20
  4. package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +24 -6
  5. package/lib/commonjs/network/components/NetworkFilterViewV3.js +12 -12
  6. package/lib/commonjs/network/components/NetworkHeaderLive.js +25 -29
  7. package/lib/commonjs/network/components/NetworkHeaderLive.web.js +24 -6
  8. package/lib/commonjs/network/components/NetworkHeaderMenu.js +10 -9
  9. package/lib/commonjs/network/components/NetworkListScreen.js +2 -1
  10. package/lib/commonjs/network/components/NetworkListScreen.web.js +24 -6
  11. package/lib/commonjs/network/components/NetworkModal.js +45 -31
  12. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.js +10 -7
  13. package/lib/commonjs/network/components/NetworkOverridesButton.js +7 -7
  14. package/lib/commonjs/network/sync/networkSyncAdapter.js +36 -16
  15. package/lib/commonjs/network/utils/networkEventStore.js +26 -0
  16. package/lib/commonjs/network/utils/networkListener.js +173 -39
  17. package/lib/module/network/components/NetworkDetailStepper.js +2 -1
  18. package/lib/module/network/components/NetworkEventDetailView.js +75 -73
  19. package/lib/module/network/components/NetworkEventItemCompact.js +25 -21
  20. package/lib/module/network/components/NetworkEventItemCompact.web.js +24 -6
  21. package/lib/module/network/components/NetworkFilterViewV3.js +13 -13
  22. package/lib/module/network/components/NetworkHeaderLive.js +26 -30
  23. package/lib/module/network/components/NetworkHeaderLive.web.js +24 -6
  24. package/lib/module/network/components/NetworkHeaderMenu.js +11 -10
  25. package/lib/module/network/components/NetworkListScreen.js +3 -2
  26. package/lib/module/network/components/NetworkListScreen.web.js +24 -6
  27. package/lib/module/network/components/NetworkModal.js +46 -32
  28. package/lib/module/network/components/NetworkOverrideHeaderButton.js +11 -8
  29. package/lib/module/network/components/NetworkOverridesButton.js +8 -8
  30. package/lib/module/network/sync/networkSyncAdapter.js +37 -17
  31. package/lib/module/network/utils/networkEventStore.js +26 -0
  32. package/lib/module/network/utils/networkListener.js +171 -38
  33. package/lib/typescript/network/components/NetworkDetailStepper.d.ts.map +1 -1
  34. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -1
  35. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -1
  36. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +1 -1
  37. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +1 -1
  38. package/lib/typescript/network/components/NetworkHeaderLive.d.ts.map +1 -1
  39. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +1 -1
  40. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
  41. package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +1 -1
  42. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  43. package/lib/typescript/network/sync/networkSyncAdapter.d.ts +1 -0
  44. package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
  45. package/lib/typescript/network/utils/networkEventStore.d.ts +17 -0
  46. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  47. package/lib/typescript/network/utils/networkListener.d.ts +61 -5
  48. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  49. package/package.json +2 -2
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useRef, useEffect, useCallback } from "react";
4
4
  import { View, StyleSheet, TouchableOpacity, TextInput } from "react-native";
5
- import { Search, Filter, Bookmark, X, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, persistentStorage, TabSelector, useFeatureGate, UpgradeModal, PowerToggleButton, useIgnoredPatterns, ToolBackground } from "@buoy-gg/shared-ui";
5
+ import { Search, Filter, Bookmark, X, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, night, persistentStorage, NightSegmented, useFeatureGate, UpgradeModal, PowerToggleButton, useIgnoredPatterns } from "@buoy-gg/shared-ui";
6
6
  import { NetworkFilterViewV3 } from "./NetworkFilterViewV3";
7
7
  import { TickProvider } from "../hooks/useTickEveryMinute";
8
8
  import { NetworkEventDetailView } from "./NetworkEventDetailView";
@@ -360,25 +360,26 @@ function NetworkModalInner({
360
360
 
361
361
  // Compact header with actions (like Sentry/Storage modals)
362
362
  const renderHeaderContent = () => {
363
- // Filter view header with tabs
363
+ // Filter view header with tabs — the night segmented control (the same
364
+ // one the dial settings header uses).
364
365
  if (showFilterView) {
365
- const tabs = [{
366
- key: "filters",
367
- label: "Filters"
368
- }, {
369
- key: "copy",
370
- label: "Copy"
371
- }];
372
366
  return /*#__PURE__*/_jsxs(ModalHeader, {
373
367
  children: [/*#__PURE__*/_jsx(ModalHeader.Navigation, {
374
368
  onBack: () => setShowFilterView(false)
375
369
  }), /*#__PURE__*/_jsx(ModalHeader.Content, {
376
370
  title: "",
377
371
  noMargin: true,
378
- children: /*#__PURE__*/_jsx(TabSelector, {
379
- tabs: tabs,
380
- activeTab: activeTab,
381
- onTabChange: tab => setActiveTab(tab)
372
+ children: /*#__PURE__*/_jsx(NightSegmented, {
373
+ size: "sm",
374
+ tabs: [{
375
+ key: "filters",
376
+ label: "Filters"
377
+ }, {
378
+ key: "copy",
379
+ label: "Copy"
380
+ }],
381
+ activeKey: activeTab,
382
+ onChange: tab => setActiveTab(tab)
382
383
  })
383
384
  })]
384
385
  });
@@ -444,12 +445,12 @@ function NetworkModalInner({
444
445
  style: styles.headerSearchContainer,
445
446
  children: [/*#__PURE__*/_jsx(Search, {
446
447
  size: 14,
447
- color: macOSColors.text.secondary
448
+ color: night.color.textSecondary
448
449
  }), /*#__PURE__*/_jsx(TextInput, {
449
450
  ref: searchInputRef,
450
451
  style: styles.headerSearchInput,
451
452
  placeholder: "Search URL, method, operation, error...",
452
- placeholderTextColor: macOSColors.text.muted,
453
+ placeholderTextColor: night.color.placeholder,
453
454
  value: searchText,
454
455
  onChangeText: handleSearch,
455
456
  onSubmitEditing: () => setIsSearchActive(false),
@@ -468,7 +469,7 @@ function NetworkModalInner({
468
469
  style: styles.headerSearchClear,
469
470
  children: /*#__PURE__*/_jsx(X, {
470
471
  size: 14,
471
- color: macOSColors.text.secondary
472
+ color: night.color.textSecondary
472
473
  })
473
474
  }) : null]
474
475
  }) : /*#__PURE__*/_jsx(NetworkHeaderBadges, {
@@ -484,7 +485,7 @@ function NetworkModalInner({
484
485
  style: styles.headerActionButton,
485
486
  children: /*#__PURE__*/_jsx(Search, {
486
487
  size: 14,
487
- color: macOSColors.text.secondary
488
+ color: night.color.textSecondary
488
489
  })
489
490
  }), overridesAvailable ? /*#__PURE__*/_jsx(NetworkOverridesButton, {
490
491
  onPress: () => setShowOverridesView(true)
@@ -520,7 +521,7 @@ function NetworkModalInner({
520
521
  const body = /*#__PURE__*/_jsxs(_Fragment, {
521
522
  children: [/*#__PURE__*/_jsxs(View, {
522
523
  style: styles.container,
523
- children: [/*#__PURE__*/_jsx(ToolBackground, {}), /*#__PURE__*/_jsx(NetworkDetailScreen, {
524
+ children: [/*#__PURE__*/_jsx(NetworkDetailScreen, {
524
525
  visible: hasSelection,
525
526
  paused: paused,
526
527
  ignoredPatternValues: ignoredPatternValues,
@@ -590,7 +591,7 @@ function NetworkModalInner({
590
591
  children: [/*#__PURE__*/_jsx(NetworkHeaderMenuItem, {
591
592
  icon: /*#__PURE__*/_jsx(Filter, {
592
593
  size: 14,
593
- color: hasActiveFilter ? macOSColors.semantic.info : macOSColors.text.secondary
594
+ color: hasActiveFilter ? night.color.accent : night.color.textSecondary
594
595
  }),
595
596
  label: "Filters",
596
597
  detail: hasActiveFilter ? "Active" : undefined,
@@ -602,7 +603,7 @@ function NetworkModalInner({
602
603
  }), /*#__PURE__*/_jsx(NetworkHeaderMenuItem, {
603
604
  icon: /*#__PURE__*/_jsx(Bookmark, {
604
605
  size: 14,
605
- color: macOSColors.text.secondary
606
+ color: night.color.textSecondary
606
607
  }),
607
608
  label: "Saved requests",
608
609
  onPress: () => {
@@ -654,6 +655,7 @@ function NetworkModalInner({
654
655
  onClose: onClose,
655
656
  onMinimize: onMinimize,
656
657
  persistenceKey: persistenceKey,
658
+ variant: "night",
657
659
  header: {
658
660
  showToggleButton: true,
659
661
  customContent: renderHeaderContent()
@@ -662,7 +664,12 @@ function NetworkModalInner({
662
664
  enablePersistence: true,
663
665
  initialMode: "bottomSheet",
664
666
  enableGlitchEffects: true,
665
- disableScrollWrapper: bodyOwnsScroll,
667
+ disableScrollWrapper: bodyOwnsScroll
668
+ // Night hoists the ToolBackground to the sheet root (field runs through
669
+ // the header). The desktop INLINE panel never goes through JsModal — its
670
+ // host (the workbench) hoists one shared field instead, so the body
671
+ // mounts none of its own in either shell.
672
+ ,
666
673
  styles: {},
667
674
  children: body
668
675
  });
@@ -670,7 +677,10 @@ function NetworkModalInner({
670
677
  const styles = StyleSheet.create({
671
678
  container: {
672
679
  flex: 1,
673
- backgroundColor: macOSColors.background.base
680
+ // Transparent so the HOST's hoisted star field shows through — the night
681
+ // JsModal's sheet-root field in the modal, the desktop workbench's shared
682
+ // row field in the inline panel.
683
+ backgroundColor: "transparent"
674
684
  },
675
685
  /** Detail = scrolling body + pinned stepper footer. `minHeight: 0` so the
676
686
  * body's ScrollView scrolls instead of stretching the column. */
@@ -683,11 +693,13 @@ const styles = StyleSheet.create({
683
693
  flex: 1
684
694
  },
685
695
  /** Inline shell — fills the host panel; `minHeight: 0` so the list's own
686
- * FlatList can scroll instead of stretching the column. */
696
+ * FlatList can scroll instead of stretching the column. Transparent so the
697
+ * desktop workbench's hoisted sky (one field spanning sidebar + panel)
698
+ * shows through — the shell paints its own base beneath that field. */
687
699
  inlineRoot: {
688
700
  flex: 1,
689
701
  minHeight: 0,
690
- backgroundColor: macOSColors.background.base
702
+ backgroundColor: "transparent"
691
703
  },
692
704
  inlineHeader: {
693
705
  paddingHorizontal: 12,
@@ -696,19 +708,21 @@ const styles = StyleSheet.create({
696
708
  borderBottomColor: macOSColors.border.default,
697
709
  backgroundColor: macOSColors.background.card
698
710
  },
711
+ // Night chrome — the header is glass over the star field, so the search
712
+ // field and action chips sit on the Everlights dark-glass fill.
699
713
  headerSearchContainer: {
700
714
  flexDirection: "row",
701
715
  alignItems: "center",
702
- backgroundColor: macOSColors.background.input,
703
- borderRadius: 10,
716
+ backgroundColor: night.color.surfaceElevated,
717
+ borderRadius: night.radius.row,
704
718
  borderWidth: 1,
705
- borderColor: macOSColors.border.default,
719
+ borderColor: night.color.border,
706
720
  paddingHorizontal: 12,
707
721
  paddingVertical: 5
708
722
  },
709
723
  headerSearchInput: {
710
724
  flex: 1,
711
- color: macOSColors.text.primary,
725
+ color: night.color.text,
712
726
  fontSize: 13,
713
727
  marginLeft: 6,
714
728
  paddingVertical: 2
@@ -724,15 +738,15 @@ const styles = StyleSheet.create({
724
738
  width: 32,
725
739
  height: 32,
726
740
  borderRadius: 8,
727
- backgroundColor: macOSColors.background.hover,
741
+ backgroundColor: night.color.buttonSurface,
728
742
  borderWidth: 1,
729
- borderColor: macOSColors.border.default,
743
+ borderColor: night.color.border,
730
744
  alignItems: "center",
731
745
  justifyContent: "center"
732
746
  },
733
747
  activeFilterButton: {
734
- backgroundColor: macOSColors.semantic.infoBackground,
735
- borderColor: macOSColors.semantic.info + "40"
748
+ backgroundColor: night.color.accentSoft,
749
+ borderColor: night.color.accentBorderStrong
736
750
  }
737
751
  });
738
752
 
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import { StyleSheet, Text, TouchableOpacity } from "react-native";
4
- import { macOSColors } from "@buoy-gg/shared-ui";
4
+ import { night } from "@buoy-gg/shared-ui";
5
5
  import { useOverrideRules } from "../hooks/useOverrideRules";
6
6
  import { useSelectedNetworkEventId } from "../hooks/useNetworkSelection";
7
7
  import { ruleMatches } from "../overrides/matchRule";
@@ -76,24 +76,27 @@ export function NetworkOverrideHeaderButton({
76
76
  });
77
77
  }
78
78
  const styles = StyleSheet.create({
79
+ // Night: the header's dark-glass chip, with the accent label the night
80
+ // language gives labelled actions (plate + accent ink). Active keeps the
81
+ // warning tint — same "you are not seeing the truth" signal as the flask.
79
82
  button: {
80
83
  paddingHorizontal: 10,
81
84
  paddingVertical: 6,
82
- borderRadius: 6,
85
+ borderRadius: 8,
83
86
  borderWidth: 1,
84
- borderColor: macOSColors.border.input,
85
- backgroundColor: macOSColors.background.input
87
+ borderColor: night.color.border,
88
+ backgroundColor: night.color.buttonSurface
86
89
  },
87
90
  buttonActive: {
88
- borderColor: macOSColors.semantic.warning + "77",
89
- backgroundColor: macOSColors.semantic.warningBackground
91
+ borderColor: night.color.warning + "77",
92
+ backgroundColor: night.color.warningSoft
90
93
  },
91
94
  label: {
92
95
  fontSize: 11,
93
96
  fontWeight: "700",
94
- color: macOSColors.text.primary
97
+ color: night.color.accent
95
98
  },
96
99
  labelActive: {
97
- color: macOSColors.semantic.warning
100
+ color: night.color.warning
98
101
  }
99
102
  });
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
4
- import { FlaskConical, macOSColors } from "@buoy-gg/shared-ui";
4
+ import { FlaskConical, night } from "@buoy-gg/shared-ui";
5
5
  import { useOverrideRules } from "../hooks/useOverrideRules";
6
6
 
7
7
  /**
@@ -32,7 +32,7 @@ export function NetworkOverridesButton({
32
32
  accessibilityLabel: active ? `Response overrides — ${activeCount} active` : "Response overrides",
33
33
  children: [/*#__PURE__*/_jsx(FlaskConical, {
34
34
  size: 14,
35
- color: active ? macOSColors.semantic.warning : macOSColors.text.secondary
35
+ color: active ? night.color.warning : night.color.textSecondary
36
36
  }), active ? /*#__PURE__*/_jsx(View, {
37
37
  style: styles.badge,
38
38
  children: /*#__PURE__*/_jsx(Text, {
@@ -53,15 +53,15 @@ const styles = StyleSheet.create({
53
53
  borderRadius: 8,
54
54
  alignItems: "center",
55
55
  justifyContent: "center",
56
- backgroundColor: macOSColors.background.hover,
56
+ backgroundColor: night.color.buttonSurface,
57
57
  borderWidth: 1,
58
- borderColor: macOSColors.border.default
58
+ borderColor: night.color.border
59
59
  },
60
60
  // Active fills the whole box, the way the power toggle goes green — the
61
61
  // warning now reads at a glance instead of as a tinted glyph.
62
62
  buttonActive: {
63
- backgroundColor: macOSColors.semantic.warningBackground,
64
- borderColor: macOSColors.semantic.warning + "77"
63
+ backgroundColor: night.color.warningSoft,
64
+ borderColor: night.color.warning + "77"
65
65
  },
66
66
  badge: {
67
67
  position: "absolute",
@@ -75,11 +75,11 @@ const styles = StyleSheet.create({
75
75
  paddingHorizontal: 3,
76
76
  alignItems: "center",
77
77
  justifyContent: "center",
78
- backgroundColor: macOSColors.semantic.warning
78
+ backgroundColor: night.color.warning
79
79
  },
80
80
  badgeText: {
81
81
  fontSize: 8,
82
82
  fontWeight: "800",
83
- color: macOSColors.background.base
83
+ color: "#000000"
84
84
  }
85
85
  });
@@ -5,7 +5,7 @@ import { flagsForEventId, networkSavedStore } from "../utils/networkSavedStore";
5
5
  import { overrideRulesStore } from "../overrides/overrideRulesStore";
6
6
  import { draftFromEvent } from "../overrides/presets";
7
7
  import { inspectEngine } from "../overrides/engine";
8
- import { getNetworkListenerCount, isNetworkListening } from "../utils/networkListener";
8
+ import { getNetworkListenerCount, isNetworkInterceptionLive, isNetworkListening } from "../utils/networkListener";
9
9
  import { approxJsonSize, isOverWireBudget } from "@buoy-gg/shared-ui";
10
10
 
11
11
  /**
@@ -361,22 +361,30 @@ export const networkSyncAdapter = {
361
361
  // flags, and a total snapshot budget so the list degrades instead of the
362
362
  // whole panel being dropped at the emit layer.
363
363
  version: 5,
364
- getSnapshot: () => ({
365
- events: fitEventsToBudget(networkEventStore.getEvents()),
366
- /**
367
- * Response-override rules live on the DEVICE — that's the only place they
368
- * can run, since a rule is applied inside the app's own XMLHttpRequest.
369
- * Remote surfaces mirror this and forward their edits back as actions.
370
- */
371
- overrides: stripLargeRuleBodies(overrideRulesStore.getState()),
372
- // Saved snapshots go through the same body strip. They are few (capped in
373
- // the store) but they ride on EVERY snapshot, so a fat one would land in
374
- // the same 200ms loop that the body-stripping exists to protect.
375
- saved: networkSavedStore.getRecords().map(record => ({
376
- ...record,
377
- event: toWireEvent(record.event).event
378
- }))
379
- }),
364
+ getSnapshot: () => {
365
+ // Piggyback the interception health check on the watch loop: while a
366
+ // dashboard/MCP watch is open this runs continuously, so a clobbered
367
+ // fetch/XHR patch (see networkListener.startListening) heals within one
368
+ // snapshot tick instead of staying dead for the session. Two property
369
+ // reads when healthy.
370
+ networkEventStore.ensureInterception();
371
+ return {
372
+ events: fitEventsToBudget(networkEventStore.getEvents()),
373
+ /**
374
+ * Response-override rules live on the DEVICE — that's the only place they
375
+ * can run, since a rule is applied inside the app's own XMLHttpRequest.
376
+ * Remote surfaces mirror this and forward their edits back as actions.
377
+ */
378
+ overrides: stripLargeRuleBodies(overrideRulesStore.getState()),
379
+ // Saved snapshots go through the same body strip. They are few (capped in
380
+ // the store) but they ride on EVERY snapshot, so a fat one would land in
381
+ // the same 200ms loop that the body-stripping exists to protect.
382
+ saved: networkSavedStore.getRecords().map(record => ({
383
+ ...record,
384
+ event: toWireEvent(record.event).event
385
+ }))
386
+ };
387
+ },
380
388
  subscribe: onChange => {
381
389
  // Pinning with no traffic in flight still has to reach the dashboard, so
382
390
  // both stores drive the snapshot.
@@ -587,10 +595,22 @@ export const networkSyncAdapter = {
587
595
  } catch {
588
596
  /* no interceptable runtime here */
589
597
  }
598
+ // `interceptorInstalled` is the listener's own flag; `interceptorLive`
599
+ // is whether the interceptors are actually reachable through the global
600
+ // chain. installed:true + live:false = something re-assigned fetch/XHR
601
+ // over the interceptor (it self-heals on the next subscribe/snapshot —
602
+ // see ensureInterception).
603
+ let interceptorLive = null;
604
+ try {
605
+ interceptorLive = isNetworkInterceptionLive();
606
+ } catch {
607
+ /* no interceptable runtime here */
608
+ }
590
609
  return {
591
610
  capturing: subscribers > 0,
592
611
  subscribers,
593
612
  interceptorInstalled,
613
+ interceptorLive,
594
614
  listenerCount,
595
615
  eventCount: networkEventStore.getEvents().length
596
616
  };
@@ -172,6 +172,32 @@ class NetworkEventStore extends BaseEventStore {
172
172
  return this.rawListenerUnsubscribe !== null;
173
173
  }
174
174
 
175
+ /**
176
+ * Re-assert the interceptor if something re-assigned fetch/XHR since capture
177
+ * started (Expo's runtime, RN DevTools, a second dev server's tooling — see
178
+ * networkListener.startListening). A no-op costing two property reads when
179
+ * everything is healthy, and a no-op entirely unless this store is actively
180
+ * capturing — a mirror-mode dashboard must never install an interceptor in
181
+ * its own process.
182
+ */
183
+ ensureInterception() {
184
+ if (!this.isCapturing()) return;
185
+ networkListener().startListening();
186
+ }
187
+
188
+ /**
189
+ * Every new consumer — the Network modal opening, a badge mounting, a
190
+ * dashboard watch attaching — is a chance to notice and repair a clobbered
191
+ * interceptor, which matters because a long-lived watch can hold the
192
+ * subscriber count above zero for the whole session (so the 0→1
193
+ * startCapturing path would never run again).
194
+ */
195
+ subscribeToEvents(listener) {
196
+ const unsubscribe = super.subscribeToEvents(listener);
197
+ this.ensureInterception();
198
+ return unsubscribe;
199
+ }
200
+
175
201
  /**
176
202
  * Process a network listener event
177
203
  */