@buoy-gg/route-events 7.0.33 → 7.0.35

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 (29) hide show
  1. package/lib/commonjs/components/NavigationStack.js +11 -11
  2. package/lib/commonjs/components/ReactNavigationRoutes.js +15 -15
  3. package/lib/commonjs/components/RouteEventDetail.js +1 -1
  4. package/lib/commonjs/components/RouteEventExpandedContent.js +3 -3
  5. package/lib/commonjs/components/RouteEventItemCompact.js +2 -2
  6. package/lib/commonjs/components/RouteEventsModalWithTabs.js +24 -107
  7. package/lib/commonjs/components/RouteEventsTimeline.js +4 -13
  8. package/lib/commonjs/components/RoutesSitemap.js +16 -16
  9. package/lib/commonjs/hooks/useRouteEvents.js +12 -43
  10. package/lib/commonjs/preset.js +7 -21
  11. package/lib/module/components/NavigationStack.js +12 -12
  12. package/lib/module/components/ReactNavigationRoutes.js +16 -16
  13. package/lib/module/components/RouteEventDetail.js +2 -2
  14. package/lib/module/components/RouteEventExpandedContent.js +4 -4
  15. package/lib/module/components/RouteEventItemCompact.js +3 -3
  16. package/lib/module/components/RouteEventsModalWithTabs.js +26 -109
  17. package/lib/module/components/RouteEventsTimeline.js +5 -14
  18. package/lib/module/components/RoutesSitemap.js +17 -17
  19. package/lib/module/hooks/useRouteEvents.js +12 -43
  20. package/lib/module/preset.js +6 -19
  21. package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
  22. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +1 -1
  23. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
  24. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +1 -1
  25. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -1
  26. package/lib/typescript/hooks/useRouteEvents.d.ts.map +1 -1
  27. package/lib/typescript/preset.d.ts +2 -8
  28. package/lib/typescript/preset.d.ts.map +1 -1
  29. package/package.json +5 -5
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.useRouteEvents = useRouteEvents;
7
- var _react = require("react");
7
+ var _sharedUi = require("@buoy-gg/shared-ui");
8
8
  var _routeEventStore = require("../stores/routeEventStore");
9
9
  /**
10
10
  * useRouteEvents Hook
@@ -39,50 +39,19 @@ function useRouteEvents(options = {}) {
39
39
  maxEvents = 500,
40
40
  enabled = true
41
41
  } = options;
42
- const [events, setEvents] = (0, _react.useState)([]);
43
- // Track listening state locally so it updates reactively with the subscription
44
- const [isListening, setIsListening] = (0, _react.useState)(enabled);
45
-
46
- // Subscribe to store - this automatically starts/stops listening
47
- // based on subscriber count (Subscribable pattern from TanStack Query)
48
- // Only subscribe when enabled is true
49
- (0, _react.useEffect)(() => {
50
- if (!enabled) {
51
- setIsListening(false);
52
- return;
53
- }
54
-
55
- // Frame-coalesced (see network's useNetworkEventList): navigation
56
- // bursts land as one commit per frame instead of one per emission.
57
- let rafId = null;
58
- let pending = null;
59
- const unsubscribe = _routeEventStore.routeEventStore.subscribeToEvents(allEvents => {
60
- pending = allEvents;
61
- if (rafId != null) return;
62
- rafId = requestAnimationFrame(() => {
63
- rafId = null;
64
- if (!pending) return;
65
- const latest = pending;
66
- pending = null;
67
- setEvents(latest.length > maxEvents ? latest.slice(0, maxEvents) : latest);
68
- });
69
- });
70
-
71
- // Mark as listening after successful subscription
72
- setIsListening(true);
73
- return () => {
74
- unsubscribe();
75
- if (rafId != null) cancelAnimationFrame(rafId);
76
- // Note: Don't set isListening=false here because we might be
77
- // re-subscribing due to dependency change, not actually stopping
78
- };
79
- }, [maxEvents, enabled]);
80
- const clearEvents = (0, _react.useCallback)(() => {
81
- _routeEventStore.routeEventStore.clearEvents();
82
- }, []);
42
+ // Subscribing auto-starts the route observer (Subscribable pattern), and
43
+ // the shared hook frame-coalesces navigation bursts into one commit.
44
+ const {
45
+ events,
46
+ clearEvents,
47
+ isActive
48
+ } = (0, _sharedUi.useEventStoreEvents)(_routeEventStore.routeEventStore, {
49
+ maxEvents,
50
+ enabled
51
+ });
83
52
  return {
84
53
  events,
85
54
  clearEvents,
86
- isListening
55
+ isListening: isActive
87
56
  };
88
57
  }
@@ -3,10 +3,10 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.createRouteEventsTool = createRouteEventsTool;
7
- exports.routeEventsToolPreset = void 0;
6
+ exports.routeEventsToolPreset = exports.createRouteEventsTool = void 0;
8
7
  var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
9
8
  var _RouteEventsModalWithTabs = require("./components/RouteEventsModalWithTabs");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
10
  var _jsxRuntime = require("react/jsx-runtime");
11
11
  /**
12
12
  * Pre-configured route events tool for FloatingDevTools
@@ -33,7 +33,7 @@ var _jsxRuntime = require("react/jsx-runtime");
33
33
  * - Navigation stack visualization
34
34
  * - Automatic route tracking (no setup needed)
35
35
  */
36
- const routeEventsToolPreset = exports.routeEventsToolPreset = {
36
+ const routeEventsToolPreset = exports.routeEventsToolPreset = (0, _sharedUi.defineToolPreset)({
37
37
  id: "route-events",
38
38
  name: "ROUTES",
39
39
  description: "Route tracking & navigation inspector",
@@ -47,7 +47,7 @@ const routeEventsToolPreset = exports.routeEventsToolPreset = {
47
47
  props: {
48
48
  enableSharedModalDimensions: false
49
49
  }
50
- };
50
+ });
51
51
 
52
52
  /**
53
53
  * Create a custom route events tool configuration.
@@ -64,20 +64,6 @@ const routeEventsToolPreset = exports.routeEventsToolPreset = {
64
64
  * });
65
65
  * ```
66
66
  */
67
- function createRouteEventsTool(options) {
68
- return {
69
- id: options?.id || "route-events",
70
- name: options?.name || "ROUTES",
71
- description: options?.description || "Route tracking & navigation inspector",
72
- slot: "both",
73
- icon: ({
74
- size
75
- }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_floatingToolsCore.RoutesIcon, {
76
- size: size
77
- }),
78
- component: _RouteEventsModalWithTabs.RouteEventsModalWithTabs,
79
- props: {
80
- enableSharedModalDimensions: options?.enableSharedModalDimensions !== undefined ? options.enableSharedModalDimensions : false
81
- }
82
- };
83
- }
67
+ const createRouteEventsTool = exports.createRouteEventsTool = (0, _sharedUi.createToolFactory)(routeEventsToolPreset, options => ({
68
+ enableSharedModalDimensions: options.enableSharedModalDimensions ?? false
69
+ }));
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { useState, useMemo, useEffect } from "react";
11
11
  import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Alert } from "react-native";
12
- import { ChevronDown, ChevronRight, InlineCopyButton, ProUpgradeModal, buoyColors, night, useSafeAreaInsets, ToolEmptyState } from "@buoy-gg/shared-ui";
12
+ import { ChevronDown, ChevronRight, InlineCopyButton, ProUpgradeModal, buoyColors, night, useSafeAreaInsets, ToolEmptyState, monoFont } from "@buoy-gg/shared-ui";
13
13
  import { useIsPro } from "@buoy-gg/license";
14
14
  import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
15
15
  import { useNavigationStack } from "../useNavigationStack";
@@ -412,7 +412,7 @@ const styles = StyleSheet.create({
412
412
  loadingText: {
413
413
  color: night.color.textSecondary,
414
414
  fontSize: 14,
415
- fontFamily: "monospace"
415
+ fontFamily: monoFont
416
416
  },
417
417
  errorContainer: {
418
418
  flex: 1,
@@ -424,13 +424,13 @@ const styles = StyleSheet.create({
424
424
  color: buoyColors.error,
425
425
  fontSize: 16,
426
426
  fontWeight: "600",
427
- fontFamily: "monospace",
427
+ fontFamily: monoFont,
428
428
  marginBottom: 8
429
429
  },
430
430
  errorDetail: {
431
431
  color: night.color.textSecondary,
432
432
  fontSize: 12,
433
- fontFamily: "monospace",
433
+ fontFamily: monoFont,
434
434
  textAlign: "center"
435
435
  },
436
436
  emptyContainer: {
@@ -443,13 +443,13 @@ const styles = StyleSheet.create({
443
443
  color: night.color.text,
444
444
  fontSize: 16,
445
445
  fontWeight: "600",
446
- fontFamily: "monospace",
446
+ fontFamily: monoFont,
447
447
  marginBottom: 8
448
448
  },
449
449
  emptySubtext: {
450
450
  color: night.color.textSecondary,
451
451
  fontSize: 14,
452
- fontFamily: "monospace",
452
+ fontFamily: monoFont,
453
453
  textAlign: "center"
454
454
  },
455
455
  stackScroll: {
@@ -487,7 +487,7 @@ const styles = StyleSheet.create({
487
487
  fontSize: 13,
488
488
  fontWeight: "600",
489
489
  color: night.color.text,
490
- fontFamily: "monospace",
490
+ fontFamily: monoFont,
491
491
  flex: 1
492
492
  },
493
493
  copyRouteButton: {
@@ -503,7 +503,7 @@ const styles = StyleSheet.create({
503
503
  statusBadgeText: {
504
504
  fontSize: 10,
505
505
  fontWeight: "600",
506
- fontFamily: "monospace"
506
+ fontFamily: monoFont
507
507
  },
508
508
  expandedContent: {
509
509
  paddingHorizontal: 12,
@@ -518,13 +518,13 @@ const styles = StyleSheet.create({
518
518
  detailLabel: {
519
519
  fontSize: 10,
520
520
  color: night.color.textSecondary,
521
- fontFamily: "monospace",
521
+ fontFamily: monoFont,
522
522
  marginBottom: 2
523
523
  },
524
524
  detailValue: {
525
525
  fontSize: 12,
526
526
  color: night.color.text,
527
- fontFamily: "monospace"
527
+ fontFamily: monoFont
528
528
  },
529
529
  dataViewerContainer: {
530
530
  marginTop: 4,
@@ -553,7 +553,7 @@ const styles = StyleSheet.create({
553
553
  fontSize: 12,
554
554
  fontWeight: "600",
555
555
  color: night.color.text,
556
- fontFamily: "monospace"
556
+ fontFamily: monoFont
557
557
  },
558
558
  actionButtonTextDisabled: {
559
559
  color: night.color.textTertiary
@@ -561,7 +561,7 @@ const styles = StyleSheet.create({
561
561
  helpText: {
562
562
  fontSize: 9,
563
563
  color: night.color.textSecondary,
564
- fontFamily: "monospace",
564
+ fontFamily: monoFont,
565
565
  textAlign: "center",
566
566
  marginTop: 4,
567
567
  lineHeight: 12
@@ -18,7 +18,7 @@ import { getCommonActions, useSafeNavigationContainerRef } from "../utils/safeRe
18
18
  // (useSafeNavigationContainerRef then yields undefined, so the dispatch
19
19
  // paths below never execute).
20
20
  const CommonActions = getCommonActions();
21
- import { Search, ChevronDown, ChevronRight, InlineCopyButton, ToolbarCopyButton, RefreshCw, ProUpgradeModal, buoyColors, night } from "@buoy-gg/shared-ui";
21
+ import { Search, ChevronDown, ChevronRight, InlineCopyButton, ToolbarCopyButton, RefreshCw, ProUpgradeModal, buoyColors, night, monoFont } from "@buoy-gg/shared-ui";
22
22
  import { useIsPro } from "@buoy-gg/license";
23
23
 
24
24
  // ============================================================================
@@ -546,12 +546,12 @@ const styles = StyleSheet.create({
546
546
  emptyText: {
547
547
  color: night.color.textSecondary,
548
548
  fontSize: 14,
549
- fontFamily: "monospace"
549
+ fontFamily: monoFont
550
550
  },
551
551
  emptySubtext: {
552
552
  color: night.color.textTertiary,
553
553
  fontSize: 12,
554
- fontFamily: "monospace",
554
+ fontFamily: monoFont,
555
555
  marginTop: 8
556
556
  },
557
557
  header: {
@@ -589,7 +589,7 @@ const styles = StyleSheet.create({
589
589
  actionLabel: {
590
590
  fontSize: 8,
591
591
  color: night.color.textTertiary,
592
- fontFamily: "monospace",
592
+ fontFamily: monoFont,
593
593
  textTransform: "uppercase",
594
594
  letterSpacing: 0.5
595
595
  },
@@ -616,13 +616,13 @@ const styles = StyleSheet.create({
616
616
  fontSize: 18,
617
617
  fontWeight: "800",
618
618
  color: night.color.text,
619
- fontFamily: "monospace"
619
+ fontFamily: monoFont
620
620
  },
621
621
  statLabel: {
622
622
  fontSize: 10,
623
623
  color: night.color.textTertiary,
624
624
  marginTop: 4,
625
- fontFamily: "monospace",
625
+ fontFamily: monoFont,
626
626
  textTransform: "uppercase",
627
627
  letterSpacing: 0.5
628
628
  },
@@ -638,7 +638,7 @@ const styles = StyleSheet.create({
638
638
  flex: 1,
639
639
  color: night.color.text,
640
640
  fontSize: 14,
641
- fontFamily: "monospace"
641
+ fontFamily: monoFont
642
642
  },
643
643
  clearButton: {
644
644
  padding: 4
@@ -655,7 +655,7 @@ const styles = StyleSheet.create({
655
655
  color: night.color.accent,
656
656
  fontSize: 14,
657
657
  fontWeight: "600",
658
- fontFamily: "monospace"
658
+ fontFamily: monoFont
659
659
  },
660
660
  scrollView: {
661
661
  flex: 1
@@ -677,7 +677,7 @@ const styles = StyleSheet.create({
677
677
  metaText: {
678
678
  fontSize: 12,
679
679
  color: night.color.textSecondary,
680
- fontFamily: "monospace"
680
+ fontFamily: monoFont
681
681
  },
682
682
  sourceBadge: {
683
683
  paddingHorizontal: 8,
@@ -691,7 +691,7 @@ const styles = StyleSheet.create({
691
691
  fontSize: 11,
692
692
  color: night.color.textSecondary,
693
693
  textTransform: "capitalize",
694
- fontFamily: "monospace"
694
+ fontFamily: monoFont
695
695
  },
696
696
  groupContainer: {
697
697
  marginVertical: 4
@@ -716,7 +716,7 @@ const styles = StyleSheet.create({
716
716
  fontSize: 12,
717
717
  fontWeight: "700",
718
718
  color: night.color.text,
719
- fontFamily: "monospace",
719
+ fontFamily: monoFont,
720
720
  textTransform: "uppercase",
721
721
  letterSpacing: 0.8
722
722
  },
@@ -735,7 +735,7 @@ const styles = StyleSheet.create({
735
735
  fontSize: 11,
736
736
  fontWeight: "600",
737
737
  color: "#3B82F6",
738
- fontFamily: "monospace"
738
+ fontFamily: monoFont
739
739
  },
740
740
  screensList: {
741
741
  // Transparent so the night sheet's star field shows through.
@@ -752,7 +752,7 @@ const styles = StyleSheet.create({
752
752
  groupDescriptionText: {
753
753
  fontSize: 11,
754
754
  color: night.color.textSecondary,
755
- fontFamily: "monospace",
755
+ fontFamily: monoFont,
756
756
  lineHeight: 16
757
757
  },
758
758
  screenItem: {
@@ -791,7 +791,7 @@ const styles = StyleSheet.create({
791
791
  fontSize: 13,
792
792
  fontWeight: "600",
793
793
  color: night.color.text,
794
- fontFamily: "monospace",
794
+ fontFamily: monoFont,
795
795
  flex: 1
796
796
  },
797
797
  screenHeaderActions: {
@@ -808,7 +808,7 @@ const styles = StyleSheet.create({
808
808
  statusText: {
809
809
  fontSize: 10,
810
810
  fontWeight: "600",
811
- fontFamily: "monospace"
811
+ fontFamily: monoFont
812
812
  },
813
813
  headerActionButton: {
814
814
  padding: 6,
@@ -826,7 +826,7 @@ const styles = StyleSheet.create({
826
826
  navigateButtonText: {
827
827
  fontSize: 12,
828
828
  color: night.color.accent,
829
- fontFamily: "monospace",
829
+ fontFamily: monoFont,
830
830
  fontWeight: "600"
831
831
  }
832
832
  });
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from "react-native";
13
- import { Navigation, night } from "@buoy-gg/shared-ui";
13
+ import { Navigation, night, monoFont } from "@buoy-gg/shared-ui";
14
14
  import { RouteEventExpandedContent } from "./RouteEventExpandedContent";
15
15
  import { getRouteTemplate } from "../utils/routeTemplate";
16
16
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -86,7 +86,7 @@ const styles = StyleSheet.create({
86
86
  fontWeight: "600",
87
87
  // Ink on a filled accent surface — the night accent takes dark ink.
88
88
  color: night.color.onAccent,
89
- fontFamily: "monospace",
89
+ fontFamily: monoFont,
90
90
  textTransform: "uppercase",
91
91
  letterSpacing: 0.5
92
92
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { View, Text, StyleSheet } from "react-native";
13
- import { InlineCopyButton, night } from "@buoy-gg/shared-ui";
13
+ import { InlineCopyButton, night, monoFont } from "@buoy-gg/shared-ui";
14
14
  import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
15
15
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  // Format duration in milliseconds to human-readable string
@@ -168,14 +168,14 @@ const styles = StyleSheet.create({
168
168
  detailLabel: {
169
169
  fontSize: 10,
170
170
  color: night.color.textSecondary,
171
- fontFamily: "monospace",
171
+ fontFamily: monoFont,
172
172
  fontWeight: "600",
173
173
  minWidth: 70
174
174
  },
175
175
  detailValue: {
176
176
  fontSize: 11,
177
177
  color: night.color.text,
178
- fontFamily: "monospace",
178
+ fontFamily: monoFont,
179
179
  flex: 1
180
180
  },
181
181
  copyButton: {
@@ -194,7 +194,7 @@ const styles = StyleSheet.create({
194
194
  dataViewerTitle: {
195
195
  fontSize: 11,
196
196
  color: night.color.textSecondary,
197
- fontFamily: "monospace",
197
+ fontFamily: monoFont,
198
198
  fontWeight: "600"
199
199
  }
200
200
  });
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { memo, useMemo } from "react";
10
10
  import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
11
- import { CompactRow, RelativeTime, buoyColors, night } from "@buoy-gg/shared-ui";
11
+ import { CompactRow, RelativeTime, buoyColors, night, monoFont } from "@buoy-gg/shared-ui";
12
12
  import { RouteEventExpandedContent } from "./RouteEventExpandedContent";
13
13
  import { getRouteTemplate } from "../utils/routeTemplate";
14
14
  import { eventRowTestID } from "../utils/testIDs";
@@ -187,7 +187,7 @@ const styles = StyleSheet.create({
187
187
  badgeText: {
188
188
  fontSize: 11,
189
189
  fontWeight: "600",
190
- fontFamily: "monospace"
190
+ fontFamily: monoFont
191
191
  },
192
192
  goButton: {
193
193
  backgroundColor: night.color.accent,
@@ -200,6 +200,6 @@ const styles = StyleSheet.create({
200
200
  fontWeight: "600",
201
201
  // Ink on a filled accent surface — the night accent takes dark ink.
202
202
  color: night.color.onAccent,
203
- fontFamily: "monospace"
203
+ fontFamily: monoFont
204
204
  }
205
205
  });
@@ -8,8 +8,8 @@ import { getCommonActions, useSafeNavigationContainerRef } from "../utils/safeRe
8
8
  // (useSafeNavigationContainerRef then yields undefined, so the dispatch
9
9
  // paths below never execute).
10
10
  const CommonActions = getCommonActions();
11
- import { useSafeRouter, isExpoRouterAvailable, windowDragProps, ToolEmptyState, resolveListEmptyState } from "@buoy-gg/shared-ui";
12
- import { JsModal, ModalHeader, TabSelector, formatRelativeTime, devToolsStorageKeys, Trash2, Filter, SearchBar, persistentStorage, ToolbarCopyButton, Lock, ProUpgradeModal, PowerToggleButton, buoyColors, night, TickProvider, useLimit } from "@buoy-gg/shared-ui";
11
+ import { useSafeRouter, isExpoRouterAvailable, windowDragProps, ToolEmptyState, resolveListEmptyState, usePersistedState, monoFont } from "@buoy-gg/shared-ui";
12
+ import { JsModal, ModalHeader, TabSelector, formatRelativeTime, devToolsStorageKeys, Trash2, Filter, SearchBar, ToolbarCopyButton, Lock, ProUpgradeModal, PowerToggleButton, buoyColors, night, TickProvider, useLimit } from "@buoy-gg/shared-ui";
13
13
  import { useIsPro } from "@buoy-gg/license";
14
14
 
15
15
  // Free tier limit for route events
@@ -22,6 +22,23 @@ import { ReactNavigationRoutes } from "./ReactNavigationRoutes";
22
22
  import { NavigationStack } from "./NavigationStack";
23
23
  import { RouteEventsTimeline } from "./RouteEventsTimeline";
24
24
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
25
+ /** Persistence recipes: the tab is stored as its bare name, the flag as "true"/"false". */
26
+ const TAB_PERSISTENCE = {
27
+ // Every tab the selector offers, including "stack": the save side has
28
+ // always written it, so leaving it off this list meant the Stack tab
29
+ // was persisted and then silently discarded on the next launch.
30
+ parse: raw => raw === "routes" || raw === "events" || raw === "stack" ? raw : undefined,
31
+ serialize: tab => tab
32
+ };
33
+ const BOOLEAN_PERSISTENCE = {
34
+ parse: raw => raw === "true",
35
+ serialize: value => value.toString()
36
+ };
37
+ const STRING_SET_PERSISTENCE = {
38
+ parse: raw => new Set(JSON.parse(raw)),
39
+ serialize: set => JSON.stringify(Array.from(set))
40
+ };
41
+
25
42
  /**
26
43
  * Find the path from root navigation state to a screen by name.
27
44
  * Returns array of route names from root to the target, or null if not found.
@@ -82,7 +99,7 @@ export function RouteEventsModalWithTabs({
82
99
  const router = useSafeRouter();
83
100
  const containerRef = useSafeNavigationContainerRef();
84
101
  const hasExpoRouter = isExpoRouterAvailable();
85
- const [activeTab, setActiveTab] = useState("events");
102
+ const [activeTab, setActiveTab] = usePersistedState(devToolsStorageKeys.routeEvents.activeTab(), "events", TAB_PERSISTENCE);
86
103
  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
87
104
  // Serialized stack reported up by NavigationStack so the copy button can live
88
105
  // in the shared navbar.
@@ -104,7 +121,7 @@ export function RouteEventsModalWithTabs({
104
121
  // See the RouteTracker component export for easy setup.
105
122
 
106
123
  // Local state to control subscription (true = subscribed, false = unsubscribed)
107
- const [isListeningEnabled, setIsListeningEnabled] = useState(true);
124
+ const [isListeningEnabled, setIsListeningEnabled] = usePersistedState(devToolsStorageKeys.routeEvents.isMonitoring(), true, BOOLEAN_PERSISTENCE);
108
125
 
109
126
  // Event Listener state - using centralized store via hook
110
127
  // Pass enabled option to control subscription
@@ -124,11 +141,8 @@ export function RouteEventsModalWithTabs({
124
141
  const [selectedEvent, setSelectedEvent] = useState(null);
125
142
  const [selectedVisitNumber, setSelectedVisitNumber] = useState(1);
126
143
  const [searchQuery, setSearchQuery] = useState("");
127
- const [ignoredPatterns, setIgnoredPatterns] = useState(new Set(["/_sitemap", "/api", "/__dev"]));
144
+ const [ignoredPatterns, setIgnoredPatterns] = usePersistedState(devToolsStorageKeys.routeEvents.eventFilters(), new Set(["/_sitemap", "/api", "/__dev"]), STRING_SET_PERSISTENCE);
128
145
  const lastEventRef = useRef(null);
129
- const hasLoadedFilters = useRef(false);
130
- const hasLoadedTabState = useRef(false);
131
- const hasLoadedMonitoringState = useRef(false);
132
146
  const handleModeChange = useCallback(_mode => {
133
147
  // Mode changes handled by JsModal
134
148
  }, []);
@@ -180,103 +194,6 @@ export function RouteEventsModalWithTabs({
180
194
  }
181
195
  }, [router, containerRef, hasExpoRouter, remote, isPro]);
182
196
 
183
- // Load persisted tab state on mount
184
- useEffect(() => {
185
- if (!visible || hasLoadedTabState.current) return;
186
- const loadTabState = async () => {
187
- try {
188
- const storedTab = await persistentStorage.getItem(devToolsStorageKeys.routeEvents.activeTab());
189
- // Every tab the selector offers, including "stack": the save side has
190
- // always written it, so leaving it off this list meant the Stack tab
191
- // was persisted and then silently discarded on the next launch —
192
- // you came back to Events having chosen otherwise.
193
- if (storedTab === "routes" || storedTab === "events" || storedTab === "stack") {
194
- setActiveTab(storedTab);
195
- }
196
- hasLoadedTabState.current = true;
197
- } catch (error) {
198
- // Failed to load tab state
199
- }
200
- };
201
- loadTabState();
202
- }, [visible]);
203
-
204
- // Load persisted monitoring state on mount
205
- useEffect(() => {
206
- if (!visible || hasLoadedMonitoringState.current) return;
207
- const loadMonitoringState = async () => {
208
- try {
209
- const storedMonitoring = await persistentStorage.getItem(devToolsStorageKeys.routeEvents.isMonitoring());
210
- if (storedMonitoring !== null) {
211
- const shouldMonitor = storedMonitoring === "true";
212
- setIsListeningEnabled(shouldMonitor);
213
- }
214
- hasLoadedMonitoringState.current = true;
215
- } catch (error) {
216
- // Failed to load monitoring state
217
- }
218
- };
219
- loadMonitoringState();
220
- }, [visible]);
221
-
222
- // Save tab state when it changes
223
- useEffect(() => {
224
- if (!hasLoadedTabState.current) return;
225
- const saveTabState = async () => {
226
- try {
227
- await persistentStorage.setItem(devToolsStorageKeys.routeEvents.activeTab(), activeTab);
228
- } catch (error) {
229
- // Failed to save tab state
230
- }
231
- };
232
- saveTabState();
233
- }, [activeTab]);
234
-
235
- // Save monitoring state when it changes
236
- useEffect(() => {
237
- if (!hasLoadedMonitoringState.current) return;
238
- const saveMonitoringState = async () => {
239
- try {
240
- await persistentStorage.setItem(devToolsStorageKeys.routeEvents.isMonitoring(), isListening.toString());
241
- } catch (error) {
242
- // Failed to save monitoring state
243
- }
244
- };
245
- saveMonitoringState();
246
- }, [isListening]);
247
-
248
- // Load persisted filters on mount
249
- useEffect(() => {
250
- if (!visible || hasLoadedFilters.current) return;
251
- const loadFilters = async () => {
252
- try {
253
- const storedFilters = await persistentStorage.getItem(devToolsStorageKeys.routeEvents.eventFilters());
254
- if (storedFilters) {
255
- const filters = JSON.parse(storedFilters);
256
- setIgnoredPatterns(new Set(filters));
257
- }
258
- hasLoadedFilters.current = true;
259
- } catch (error) {
260
- // Failed to load filters
261
- }
262
- };
263
- loadFilters();
264
- }, [visible]);
265
-
266
- // Save filters when they change
267
- useEffect(() => {
268
- if (!hasLoadedFilters.current) return;
269
- const saveFilters = async () => {
270
- try {
271
- const filters = Array.from(ignoredPatterns);
272
- await persistentStorage.setItem(devToolsStorageKeys.routeEvents.eventFilters(), JSON.stringify(filters));
273
- } catch (error) {
274
- // Failed to save filters
275
- }
276
- };
277
- saveFilters();
278
- }, [ignoredPatterns]);
279
-
280
197
  // Event listener is now managed by useRouteEvents hook
281
198
 
282
199
  const handleToggleListening = useCallback(() => {
@@ -726,7 +643,7 @@ const styles = StyleSheet.create({
726
643
  fontWeight: "600",
727
644
  marginTop: 16,
728
645
  marginBottom: 8,
729
- fontFamily: "monospace",
646
+ fontFamily: monoFont,
730
647
  letterSpacing: 0.5,
731
648
  textTransform: "uppercase"
732
649
  },
@@ -734,7 +651,7 @@ const styles = StyleSheet.create({
734
651
  color: night.color.textSecondary,
735
652
  fontSize: 14,
736
653
  textAlign: "center",
737
- fontFamily: "monospace"
654
+ fontFamily: monoFont
738
655
  },
739
656
  contentWrapper: {
740
657
  flex: 1
@@ -749,7 +666,7 @@ const styles = StyleSheet.create({
749
666
  statsText: {
750
667
  fontSize: 11,
751
668
  color: night.color.textSecondary,
752
- fontFamily: "monospace",
669
+ fontFamily: monoFont,
753
670
  textAlign: "center"
754
671
  },
755
672
  statsWarning: {
@@ -779,7 +696,7 @@ const styles = StyleSheet.create({
779
696
  fontSize: 12,
780
697
  fontWeight: "600",
781
698
  color: night.color.warning,
782
- fontFamily: "monospace"
699
+ fontFamily: monoFont
783
700
  },
784
701
  lockedEventsBannerSubtext: {
785
702
  fontSize: 11,