@buoy-gg/route-events 7.0.26 → 7.0.28

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 (39) hide show
  1. package/lib/commonjs/RouteParser.js +8 -4
  2. package/lib/commonjs/components/NavigationStack.js +19 -18
  3. package/lib/commonjs/components/ReactNavigationRoutes.js +108 -106
  4. package/lib/commonjs/components/RouteEventDetail.js +9 -6
  5. package/lib/commonjs/components/RouteEventExpandedContent.js +3 -3
  6. package/lib/commonjs/components/RouteEventItemCompact.js +5 -4
  7. package/lib/commonjs/components/RouteEventsModalWithTabs.js +48 -25
  8. package/lib/commonjs/components/RouteEventsTimeline.js +3 -3
  9. package/lib/commonjs/components/RoutesSitemap.js +129 -129
  10. package/lib/commonjs/sync/routeEventsSyncAdapter.js +9 -3
  11. package/lib/commonjs/useNavigationStack.js +4 -5
  12. package/lib/commonjs/useRouteObserverReactNavigation.js +1 -5
  13. package/lib/commonjs/utils/safeReactNavigation.js +62 -6
  14. package/lib/module/RouteParser.js +8 -4
  15. package/lib/module/components/NavigationStack.js +20 -19
  16. package/lib/module/components/ReactNavigationRoutes.js +111 -109
  17. package/lib/module/components/RouteEventDetail.js +10 -7
  18. package/lib/module/components/RouteEventExpandedContent.js +4 -4
  19. package/lib/module/components/RouteEventItemCompact.js +6 -5
  20. package/lib/module/components/RouteEventsModalWithTabs.js +51 -28
  21. package/lib/module/components/RouteEventsTimeline.js +4 -4
  22. package/lib/module/components/RoutesSitemap.js +131 -131
  23. package/lib/module/sync/routeEventsSyncAdapter.js +9 -3
  24. package/lib/module/useNavigationStack.js +6 -7
  25. package/lib/module/useRouteObserverReactNavigation.js +3 -7
  26. package/lib/module/utils/safeReactNavigation.js +62 -6
  27. package/lib/typescript/RouteParser.d.ts.map +1 -1
  28. package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
  29. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +1 -1
  30. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts +7 -0
  31. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
  32. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -1
  33. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts +1 -0
  34. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +1 -1
  35. package/lib/typescript/useNavigationStack.d.ts.map +1 -1
  36. package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +1 -1
  37. package/lib/typescript/utils/safeReactNavigation.d.ts +6 -5
  38. package/lib/typescript/utils/safeReactNavigation.d.ts.map +1 -1
  39. package/package.json +5 -5
@@ -12,10 +12,10 @@
12
12
  */
13
13
 
14
14
  import { useState, useCallback, useMemo, useEffect } from "react";
15
- import { macOSColors } from "@buoy-gg/shared-ui";
15
+ import { night } from "@buoy-gg/shared-ui";
16
16
  import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Alert, Platform } from "react-native";
17
17
  import { useSafeRouter } from "@buoy-gg/shared-ui";
18
- import { Search, ChevronDown, ChevronRight, Home, InlineCopyButton, CopyButton, RefreshCw, formatRelativeTime, ProUpgradeModal, buoyColors } from "@buoy-gg/shared-ui";
18
+ import { Search, ChevronDown, ChevronRight, Home, InlineCopyButton, CopyButton, RefreshCw, formatRelativeTime, ProUpgradeModal } from "@buoy-gg/shared-ui";
19
19
  import { useIsPro } from "@buoy-gg/license";
20
20
  import { useRouteSitemap } from "../useRouteSitemap";
21
21
 
@@ -55,7 +55,7 @@ export function RoutesSitemap({
55
55
  }) {
56
56
  const [searchQuery, setSearchQuery] = useState("");
57
57
  const [isSearching, setIsSearching] = useState(false);
58
- const [expandedGroups, setExpandedGroups] = useState(new Set(["Root Routes", "Dynamic Routes"]));
58
+ const [expandedGroups, setExpandedGroups] = useState(new Set(["Static Routes", "Dynamic Routes"]));
59
59
  const [isRefreshing, setIsRefreshing] = useState(false);
60
60
  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
61
61
 
@@ -261,96 +261,15 @@ export function RoutesSitemap({
261
261
  }
262
262
  return /*#__PURE__*/_jsxs(View, {
263
263
  style: [styles.container, style],
264
- children: [!isSearching ? /*#__PURE__*/_jsxs(View, {
265
- style: styles.header,
266
- children: [/*#__PURE__*/_jsxs(View, {
267
- style: styles.actionsRow,
268
- children: [/*#__PURE__*/_jsxs(View, {
269
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
270
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
271
- style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
272
- onPress: handleGoHome,
273
- accessibilityLabel: "Go to home route",
274
- children: /*#__PURE__*/_jsx(Home, {
275
- size: ACTION_ICON_SIZE,
276
- color: buoyColors.textSecondary
277
- })
278
- }), /*#__PURE__*/_jsx(Text, {
279
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
280
- children: "Home"
281
- })]
282
- }), /*#__PURE__*/_jsxs(View, {
283
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
284
- children: [/*#__PURE__*/_jsx(CopyButton, {
285
- value: copyAllData,
286
- size: IS_DESKTOP ? ACTION_ICON_SIZE : 14,
287
- buttonStyle: IS_DESKTOP ? styles.actionButtonHeaderDesktop : styles.actionButtonHeader
288
- }), /*#__PURE__*/_jsx(Text, {
289
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
290
- children: "Copy"
291
- })]
292
- }), /*#__PURE__*/_jsxs(View, {
293
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
294
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
295
- style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop, isRefreshing && styles.refreshButtonDisabled],
296
- onPress: handleManualRefresh,
297
- disabled: isRefreshing,
298
- children: /*#__PURE__*/_jsx(RefreshCw, {
299
- size: ACTION_ICON_SIZE,
300
- color: isRefreshing ? buoyColors.textMuted : buoyColors.textSecondary
301
- })
302
- }), /*#__PURE__*/_jsx(Text, {
303
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
304
- children: "Refresh"
305
- })]
306
- }), /*#__PURE__*/_jsxs(View, {
307
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
308
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
309
- style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
310
- onPress: () => setIsSearching(true),
311
- children: /*#__PURE__*/_jsx(Search, {
312
- size: ACTION_ICON_SIZE,
313
- color: buoyColors.textSecondary
314
- })
315
- }), /*#__PURE__*/_jsx(Text, {
316
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
317
- children: "Search"
318
- })]
319
- })]
320
- }), /*#__PURE__*/_jsxs(View, {
321
- style: styles.statsGrid,
322
- children: [/*#__PURE__*/_jsx(StatItem, {
323
- value: stats.total,
324
- label: "Total"
325
- }), /*#__PURE__*/_jsx(StatItem, {
326
- value: stats.static,
327
- label: "Static"
328
- }), /*#__PURE__*/_jsx(StatItem, {
329
- value: stats.dynamic,
330
- label: "Dynamic"
331
- }), /*#__PURE__*/_jsx(StatItem, {
332
- value: stats.catchAll,
333
- label: "Catch-All"
334
- }), /*#__PURE__*/_jsx(StatItem, {
335
- value: stats.layouts,
336
- label: "Layouts"
337
- }), /*#__PURE__*/_jsx(StatItem, {
338
- value: stats.groups,
339
- label: "Groups"
340
- })]
341
- })]
342
- }) :
343
- /*#__PURE__*/
344
- /* Search mode - full width search bar */
345
- _jsxs(View, {
264
+ children: [isSearching && /*#__PURE__*/_jsxs(View, {
346
265
  style: styles.searchContainer,
347
266
  children: [/*#__PURE__*/_jsx(Search, {
348
267
  size: 16,
349
- color: buoyColors.textMuted
268
+ color: night.color.textTertiary
350
269
  }), /*#__PURE__*/_jsx(TextInput, {
351
270
  style: styles.searchInput,
352
271
  placeholder: "Search routes...",
353
- placeholderTextColor: buoyColors.textMuted,
272
+ placeholderTextColor: night.color.placeholder,
354
273
  value: searchQuery,
355
274
  onChangeText: setSearchQuery,
356
275
  autoCapitalize: "none",
@@ -377,7 +296,85 @@ export function RoutesSitemap({
377
296
  }), /*#__PURE__*/_jsxs(ScrollView, {
378
297
  style: styles.scrollView,
379
298
  contentContainerStyle: styles.scrollContent,
380
- children: [displayGroups.length === 0 ? /*#__PURE__*/_jsx(View, {
299
+ children: [!isSearching && /*#__PURE__*/_jsxs(View, {
300
+ style: styles.header,
301
+ children: [/*#__PURE__*/_jsxs(View, {
302
+ style: styles.actionsRow,
303
+ children: [/*#__PURE__*/_jsxs(View, {
304
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
305
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
306
+ style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
307
+ onPress: handleGoHome,
308
+ accessibilityLabel: "Go to home route",
309
+ children: /*#__PURE__*/_jsx(Home, {
310
+ size: ACTION_ICON_SIZE,
311
+ color: night.color.textSecondary
312
+ })
313
+ }), /*#__PURE__*/_jsx(Text, {
314
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
315
+ children: "Home"
316
+ })]
317
+ }), /*#__PURE__*/_jsxs(View, {
318
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
319
+ children: [/*#__PURE__*/_jsx(CopyButton, {
320
+ value: copyAllData,
321
+ size: IS_DESKTOP ? ACTION_ICON_SIZE : 14,
322
+ buttonStyle: IS_DESKTOP ? styles.actionButtonHeaderDesktop : styles.actionButtonHeader
323
+ }), /*#__PURE__*/_jsx(Text, {
324
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
325
+ children: "Copy"
326
+ })]
327
+ }), /*#__PURE__*/_jsxs(View, {
328
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
329
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
330
+ style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop, isRefreshing && styles.refreshButtonDisabled],
331
+ onPress: handleManualRefresh,
332
+ disabled: isRefreshing,
333
+ children: /*#__PURE__*/_jsx(RefreshCw, {
334
+ size: ACTION_ICON_SIZE,
335
+ color: isRefreshing ? night.color.textTertiary : night.color.textSecondary
336
+ })
337
+ }), /*#__PURE__*/_jsx(Text, {
338
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
339
+ children: "Refresh"
340
+ })]
341
+ }), /*#__PURE__*/_jsxs(View, {
342
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
343
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
344
+ style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
345
+ onPress: () => setIsSearching(true),
346
+ children: /*#__PURE__*/_jsx(Search, {
347
+ size: ACTION_ICON_SIZE,
348
+ color: night.color.textSecondary
349
+ })
350
+ }), /*#__PURE__*/_jsx(Text, {
351
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
352
+ children: "Search"
353
+ })]
354
+ })]
355
+ }), /*#__PURE__*/_jsxs(View, {
356
+ style: styles.statsGrid,
357
+ children: [/*#__PURE__*/_jsx(StatItem, {
358
+ value: stats.total,
359
+ label: "Total"
360
+ }), /*#__PURE__*/_jsx(StatItem, {
361
+ value: stats.static,
362
+ label: "Static"
363
+ }), /*#__PURE__*/_jsx(StatItem, {
364
+ value: stats.dynamic,
365
+ label: "Dynamic"
366
+ }), /*#__PURE__*/_jsx(StatItem, {
367
+ value: stats.catchAll,
368
+ label: "Catch-All"
369
+ }), /*#__PURE__*/_jsx(StatItem, {
370
+ value: stats.layouts,
371
+ label: "Layouts"
372
+ }), /*#__PURE__*/_jsx(StatItem, {
373
+ value: stats.groups,
374
+ label: "Groups"
375
+ })]
376
+ })]
377
+ }), displayGroups.length === 0 ? /*#__PURE__*/_jsx(View, {
381
378
  style: styles.emptyState,
382
379
  children: /*#__PURE__*/_jsx(Text, {
383
380
  style: styles.emptyText,
@@ -444,10 +441,10 @@ function RouteGroupView({
444
441
  style: styles.groupHeaderLeft,
445
442
  children: [isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
446
443
  size: 14,
447
- color: buoyColors.textSecondary
444
+ color: night.color.textSecondary
448
445
  }) : /*#__PURE__*/_jsx(ChevronRight, {
449
446
  size: 14,
450
- color: buoyColors.textSecondary
447
+ color: night.color.textSecondary
451
448
  }), /*#__PURE__*/_jsx(Text, {
452
449
  style: styles.groupTitle,
453
450
  children: group.title
@@ -504,10 +501,10 @@ function RouteItemView({
504
501
  style: styles.expandIndicator,
505
502
  children: isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
506
503
  size: 14,
507
- color: buoyColors.textSecondary
504
+ color: night.color.textSecondary
508
505
  }) : /*#__PURE__*/_jsx(ChevronRight, {
509
506
  size: 14,
510
- color: buoyColors.textSecondary
507
+ color: night.color.textSecondary
511
508
  })
512
509
  }), /*#__PURE__*/_jsx(Text, {
513
510
  style: styles.routePath,
@@ -611,7 +608,8 @@ function getRouteTypeColor(type) {
611
608
  const styles = StyleSheet.create({
612
609
  container: {
613
610
  flex: 1,
614
- backgroundColor: buoyColors.base
611
+ // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
612
+ backgroundColor: "transparent"
615
613
  },
616
614
  loadingContainer: {
617
615
  flex: 1,
@@ -619,7 +617,7 @@ const styles = StyleSheet.create({
619
617
  alignItems: "center"
620
618
  },
621
619
  loadingText: {
622
- color: buoyColors.textSecondary,
620
+ color: night.color.textSecondary,
623
621
  fontSize: 14,
624
622
  fontFamily: "monospace",
625
623
  textAlign: "center",
@@ -631,8 +629,9 @@ const styles = StyleSheet.create({
631
629
  flexDirection: "column",
632
630
  padding: 8,
633
631
  borderBottomWidth: 1,
634
- borderBottomColor: buoyColors.border,
635
- gap: 12
632
+ borderBottomColor: night.color.border,
633
+ gap: 12,
634
+ marginBottom: 8
636
635
  },
637
636
  actionsRow: {
638
637
  flexDirection: "row",
@@ -651,7 +650,7 @@ const styles = StyleSheet.create({
651
650
  flexShrink: 1,
652
651
  flexBasis: "30%",
653
652
  minWidth: 90,
654
- backgroundColor: buoyColors.card,
653
+ backgroundColor: night.color.surface,
655
654
  borderRadius: 8,
656
655
  paddingVertical: 10,
657
656
  paddingHorizontal: 12,
@@ -660,12 +659,12 @@ const styles = StyleSheet.create({
660
659
  statValue: {
661
660
  fontSize: 18,
662
661
  fontWeight: "800",
663
- color: buoyColors.text,
662
+ color: night.color.text,
664
663
  fontFamily: "monospace"
665
664
  },
666
665
  statLabel: {
667
666
  fontSize: 10,
668
- color: buoyColors.textMuted,
667
+ color: night.color.textTertiary,
669
668
  marginTop: 4,
670
669
  fontFamily: "monospace",
671
670
  textTransform: "uppercase",
@@ -685,12 +684,12 @@ const styles = StyleSheet.create({
685
684
  actionButtonHeader: {
686
685
  padding: 6,
687
686
  borderRadius: 4,
688
- backgroundColor: buoyColors.input
687
+ backgroundColor: night.color.surfaceElevated
689
688
  },
690
689
  actionButtonHeaderDesktop: {
691
690
  padding: 12,
692
691
  borderRadius: 8,
693
- backgroundColor: buoyColors.input
692
+ backgroundColor: night.color.surfaceElevated
694
693
  },
695
694
  iconButton: {
696
695
  padding: 6,
@@ -701,11 +700,11 @@ const styles = StyleSheet.create({
701
700
  iconButtonDesktop: {
702
701
  padding: 12,
703
702
  borderRadius: 8,
704
- backgroundColor: buoyColors.input
703
+ backgroundColor: night.color.surfaceElevated
705
704
  },
706
705
  actionLabel: {
707
706
  fontSize: 8,
708
- color: buoyColors.textMuted,
707
+ color: night.color.textTertiary,
709
708
  fontFamily: "monospace",
710
709
  textTransform: "uppercase",
711
710
  letterSpacing: 0.5
@@ -725,11 +724,11 @@ const styles = StyleSheet.create({
725
724
  paddingVertical: 16,
726
725
  marginTop: 16,
727
726
  borderTopWidth: 1,
728
- borderTopColor: buoyColors.border
727
+ borderTopColor: night.color.border
729
728
  },
730
729
  metaText: {
731
730
  fontSize: 12,
732
- color: buoyColors.textSecondary,
731
+ color: night.color.textSecondary,
733
732
  fontFamily: "monospace"
734
733
  },
735
734
  sourceBadge: {
@@ -737,12 +736,12 @@ const styles = StyleSheet.create({
737
736
  paddingVertical: 2,
738
737
  borderRadius: 999,
739
738
  borderWidth: 1,
740
- borderColor: buoyColors.border,
741
- backgroundColor: buoyColors.card
739
+ borderColor: night.color.border,
740
+ backgroundColor: night.color.surface
742
741
  },
743
742
  sourceText: {
744
743
  fontSize: 11,
745
- color: buoyColors.textSecondary,
744
+ color: night.color.textSecondary,
746
745
  textTransform: "capitalize",
747
746
  fontFamily: "monospace"
748
747
  },
@@ -752,11 +751,11 @@ const styles = StyleSheet.create({
752
751
  padding: 8,
753
752
  gap: 8,
754
753
  borderBottomWidth: 1,
755
- borderBottomColor: buoyColors.border
754
+ borderBottomColor: night.color.border
756
755
  },
757
756
  searchInput: {
758
757
  flex: 1,
759
- color: buoyColors.text,
758
+ color: night.color.text,
760
759
  fontSize: 14,
761
760
  fontFamily: "monospace"
762
761
  },
@@ -764,7 +763,7 @@ const styles = StyleSheet.create({
764
763
  padding: 4
765
764
  },
766
765
  clearButtonText: {
767
- color: buoyColors.textMuted,
766
+ color: night.color.textTertiary,
768
767
  fontSize: 18
769
768
  },
770
769
  closeSearchButton: {
@@ -772,7 +771,7 @@ const styles = StyleSheet.create({
772
771
  paddingVertical: 4
773
772
  },
774
773
  closeSearchText: {
775
- color: buoyColors.primary,
774
+ color: night.color.accent,
776
775
  fontSize: 14,
777
776
  fontWeight: "600",
778
777
  fontFamily: "monospace"
@@ -781,14 +780,14 @@ const styles = StyleSheet.create({
781
780
  flex: 1
782
781
  },
783
782
  scrollContent: {
784
- paddingVertical: 8
783
+ paddingBottom: 8
785
784
  },
786
785
  emptyState: {
787
786
  padding: 32,
788
787
  alignItems: "center"
789
788
  },
790
789
  emptyText: {
791
- color: buoyColors.textSecondary,
790
+ color: night.color.textSecondary,
792
791
  fontSize: 14,
793
792
  fontFamily: "monospace"
794
793
  },
@@ -801,9 +800,9 @@ const styles = StyleSheet.create({
801
800
  justifyContent: "space-between",
802
801
  padding: 12,
803
802
  paddingHorizontal: 16,
804
- backgroundColor: buoyColors.card,
803
+ backgroundColor: night.color.surface,
805
804
  borderLeftWidth: 3,
806
- borderLeftColor: buoyColors.textSecondary
805
+ borderLeftColor: night.color.fillTertiary
807
806
  },
808
807
  groupHeaderLeft: {
809
808
  flexDirection: "row",
@@ -814,7 +813,7 @@ const styles = StyleSheet.create({
814
813
  groupTitle: {
815
814
  fontSize: 12,
816
815
  fontWeight: "700",
817
- color: buoyColors.text,
816
+ color: night.color.text,
818
817
  fontFamily: "monospace",
819
818
  textTransform: "uppercase",
820
819
  letterSpacing: 0.8
@@ -837,19 +836,20 @@ const styles = StyleSheet.create({
837
836
  fontFamily: "monospace"
838
837
  },
839
838
  routesList: {
840
- backgroundColor: buoyColors.base
839
+ // Transparent so the night sheet's star field shows through.
840
+ backgroundColor: "transparent"
841
841
  },
842
842
  groupDescription: {
843
843
  paddingHorizontal: 16,
844
844
  paddingVertical: 8,
845
845
  marginBottom: 8,
846
- backgroundColor: buoyColors.input,
846
+ backgroundColor: night.color.surfaceElevated,
847
847
  borderLeftWidth: 3,
848
- borderLeftColor: buoyColors.textSecondary
848
+ borderLeftColor: night.color.fillTertiary
849
849
  },
850
850
  groupDescriptionText: {
851
851
  fontSize: 11,
852
- color: buoyColors.textSecondary,
852
+ color: night.color.textSecondary,
853
853
  fontFamily: "monospace",
854
854
  lineHeight: 16
855
855
  },
@@ -857,10 +857,10 @@ const styles = StyleSheet.create({
857
857
  marginBottom: 6
858
858
  },
859
859
  routeCard: {
860
- backgroundColor: buoyColors.card,
860
+ backgroundColor: night.color.surface,
861
861
  borderRadius: 6,
862
862
  borderWidth: 1,
863
- borderColor: buoyColors.border,
863
+ borderColor: night.color.border,
864
864
  overflow: "hidden",
865
865
  shadowColor: "#000",
866
866
  shadowOffset: {
@@ -892,7 +892,7 @@ const styles = StyleSheet.create({
892
892
  routePath: {
893
893
  fontSize: 13,
894
894
  fontWeight: "600",
895
- color: buoyColors.text,
895
+ color: night.color.text,
896
896
  fontFamily: "monospace",
897
897
  flex: 1
898
898
  },
@@ -915,7 +915,7 @@ const styles = StyleSheet.create({
915
915
  childCountText: {
916
916
  fontSize: 11,
917
917
  fontWeight: "600",
918
- color: macOSColors.text.icon,
918
+ color: night.color.textSecondary,
919
919
  fontFamily: "monospace"
920
920
  },
921
921
  typeTag: {
@@ -934,9 +934,9 @@ const styles = StyleSheet.create({
934
934
  width: 28,
935
935
  height: 24,
936
936
  borderRadius: 4,
937
- backgroundColor: buoyColors.input,
937
+ backgroundColor: night.color.surfaceElevated,
938
938
  borderWidth: 1,
939
- borderColor: buoyColors.border,
939
+ borderColor: night.color.border,
940
940
  alignItems: "center",
941
941
  justifyContent: "center"
942
942
  },
@@ -947,19 +947,19 @@ const styles = StyleSheet.create({
947
947
  borderRadius: 4,
948
948
  alignItems: "center",
949
949
  justifyContent: "center",
950
- backgroundColor: buoyColors.primary + "15",
950
+ backgroundColor: night.color.accentSoft,
951
951
  borderWidth: 1,
952
- borderColor: buoyColors.primary + "40"
952
+ borderColor: night.color.accentBorder
953
953
  },
954
954
  goButtonText: {
955
955
  fontSize: 12,
956
- color: buoyColors.primary,
956
+ color: night.color.accent,
957
957
  fontFamily: "monospace",
958
958
  fontWeight: "600"
959
959
  },
960
960
  childrenContainer: {
961
961
  borderLeftWidth: 2,
962
- borderLeftColor: buoyColors.border,
962
+ borderLeftColor: night.color.border,
963
963
  marginLeft: 16,
964
964
  marginRight: 8,
965
965
  marginTop: 2,
@@ -121,7 +121,8 @@ export const routeEventsSyncAdapter = {
121
121
  * dynamic params into a concrete path before invoking this.
122
122
  */
123
123
  navigate: params => {
124
- const path = params?.path;
124
+ const p = params;
125
+ const path = p?.path;
125
126
  if (!path) {
126
127
  throw new Error("navigate requires a 'path' param");
127
128
  }
@@ -129,9 +130,14 @@ export const routeEventsSyncAdapter = {
129
130
  if (!router) {
130
131
  throw new Error("expo-router is not available on this device");
131
132
  }
132
- router.navigate(path);
133
+ // Push by default so navigate-then-back flows keep working. Callers that
134
+ // mean "leave this screen" (sitemap Go, agents resetting to home) pass
135
+ // replace: true — otherwise "Go to home" stacks another `/` on top.
136
+ const go = p?.replace === true && typeof router.replace === "function" ? router.replace : router.navigate;
137
+ go(path);
133
138
  return {
134
- navigated: path
139
+ navigated: path,
140
+ replaced: p?.replace === true
135
141
  };
136
142
  },
137
143
  // ── Stack actions: delegate to the live navigation actions captured by
@@ -12,16 +12,15 @@
12
12
  * - RN CLI: uses NavigationContainerRefContext (works as sibling of navigators)
13
13
  */
14
14
 
15
- import { useState, useContext, useEffect, useMemo, useCallback } from "react";
16
- import { getCommonActions, getStackActions, getNavigationContainerRefContext } from "./utils/safeReactNavigation";
15
+ import { useState, useEffect, useMemo, useCallback } from "react";
16
+ import { getCommonActions, getStackActions, useSafeNavigationContainerRef } from "./utils/safeReactNavigation";
17
17
  import { getSafeRouter, isExpoRouterAvailable } from "@buoy-gg/shared-ui";
18
18
 
19
- // Resolved once at module load; null when @react-navigation/native is absent
20
- // (the fallback context then yields an undefined containerRef, so the
21
- // dispatch paths below never execute).
19
+ // Resolved once at module load; null when React Navigation is absent
20
+ // (useSafeNavigationContainerRef then yields undefined, so the dispatch
21
+ // paths below never execute).
22
22
  const CommonActions = getCommonActions();
23
23
  const StackActions = getStackActions();
24
- const NavigationContainerRefContext = getNavigationContainerRefContext();
25
24
 
26
25
  // ============================================================================
27
26
  // Type Definitions
@@ -199,7 +198,7 @@ function buildStack(navigationState, isExpo) {
199
198
  */
200
199
  export function useNavigationStack() {
201
200
  const isExpo = isExpoRouterAvailable();
202
- const containerRef = useContext(NavigationContainerRefContext);
201
+ const containerRef = useSafeNavigationContainerRef();
203
202
  const [navigationState, setNavigationState] = useState(() => containerRef?.getRootState?.() ?? null);
204
203
  const [isLoaded, setIsLoaded] = useState(false);
205
204
  const [error, setError] = useState(null);
@@ -11,14 +11,10 @@
11
11
  * a NavigationContainer — does NOT need to be inside a navigator.
12
12
  */
13
13
 
14
- import { useContext, useEffect, useRef } from "react";
15
- import { getNavigationContainerRefContext } from "./utils/safeReactNavigation";
14
+ import { useEffect, useRef } from "react";
15
+ import { useSafeNavigationContainerRef } from "./utils/safeReactNavigation";
16
16
  import { routeObserver } from "./RouteObserver";
17
17
 
18
- // Resolved once at module load; falls back to an always-undefined context
19
- // when @react-navigation/native is absent, so the effect below no-ops.
20
- const NavigationContainerRefContext = getNavigationContainerRefContext();
21
-
22
18
  /**
23
19
  * Walk a React Navigation state tree to find the deepest focused route.
24
20
  * Returns the route object and the array of route names from root to leaf.
@@ -82,7 +78,7 @@ function emitRouteEvent(state, previousPathnameRef, previousTimestampRef, callba
82
78
  * Works anywhere inside a NavigationContainer (does not need to be inside a navigator).
83
79
  */
84
80
  export function useRouteObserverReactNavigation(callback) {
85
- const containerRef = useContext(NavigationContainerRefContext);
81
+ const containerRef = useSafeNavigationContainerRef();
86
82
  const callbackRef = useRef(callback);
87
83
  const previousPathnameRef = useRef(undefined);
88
84
  const previousTimestampRef = useRef(undefined);
@@ -7,10 +7,35 @@
7
7
  * Falls back to no-op implementations when @react-navigation/native is not installed.
8
8
  */
9
9
 
10
- import { createContext, useMemo } from "react";
10
+ import { createContext, useContext, useMemo } from "react";
11
11
  let reactNavigation = null;
12
12
  let isAvailable = false;
13
13
  let checkedAvailability = false;
14
+
15
+ // Expo Router (SDK 54+) VENDORS React Navigation: its NavigationContainer
16
+ // provides the vendored NavigationContainerRefContext from
17
+ // expo-router/build/react-navigation, NOT the one exported by the
18
+ // @react-navigation/native package (which may not even be installed, or is a
19
+ // second module instance whose context nobody provides). Reading only the npm
20
+ // context in such apps yields undefined forever — the Stack tab then hangs on
21
+ // "Loading navigation stack...". So we load both copies and consult both
22
+ // contexts.
23
+ let vendoredReactNavigation = null;
24
+ let checkedVendored = false;
25
+ function getVendoredReactNavigation() {
26
+ if (checkedVendored) return vendoredReactNavigation;
27
+ checkedVendored = true;
28
+ try {
29
+ // Compat entry re-exporting the vendored native/core/routers modules.
30
+ // Older expo-router versions (and RN CLI apps) don't have it — the
31
+ // require throws and we stay on @react-navigation/native.
32
+ const vendored = require("expo-router/react-navigation");
33
+ vendoredReactNavigation = vendored?.NavigationContainerRefContext ? vendored : null;
34
+ } catch {
35
+ vendoredReactNavigation = null;
36
+ }
37
+ return vendoredReactNavigation;
38
+ }
14
39
  function checkReactNavigationAvailability() {
15
40
  if (checkedAvailability) return isAvailable;
16
41
  try {
@@ -20,6 +45,17 @@ function checkReactNavigationAvailability() {
20
45
  isAvailable = false;
21
46
  reactNavigation = null;
22
47
  }
48
+
49
+ // Expo Router apps don't necessarily install @react-navigation/native at
50
+ // all — the vendored copy then supplies CommonActions/StackActions/hooks
51
+ // (actions are plain serializable objects, so either copy's work).
52
+ if (!isAvailable) {
53
+ const vendored = getVendoredReactNavigation();
54
+ if (vendored) {
55
+ reactNavigation = vendored;
56
+ isAvailable = true;
57
+ }
58
+ }
23
59
  checkedAvailability = true;
24
60
  return isAvailable;
25
61
  }
@@ -111,13 +147,33 @@ export function getStackActions() {
111
147
  return checkReactNavigationAvailability() ? reactNavigation.StackActions : null;
112
148
  }
113
149
 
150
+ // Both contexts are resolved ONCE and cached so hook identity/order stays
151
+ // stable for the app lifetime (module presence is constant).
152
+ let npmContainerRefContext = null;
153
+ let vendoredContainerRefContext = null;
154
+ function getContainerRefContexts() {
155
+ if (npmContainerRefContext == null) {
156
+ npmContainerRefContext = checkReactNavigationAvailability() && reactNavigation.NavigationContainerRefContext ? reactNavigation.NavigationContainerRefContext : FallbackNavigationContainerRefContext;
157
+ }
158
+ if (vendoredContainerRefContext == null) {
159
+ const vendored = getVendoredReactNavigation();
160
+ vendoredContainerRefContext = vendored?.NavigationContainerRefContext ?? FallbackNavigationContainerRefContext;
161
+ }
162
+ return [npmContainerRefContext, vendoredContainerRefContext];
163
+ }
164
+
114
165
  /**
115
- * NavigationContainerRefContext, read via @react-navigation/native's
116
- * re-export (never from @react-navigation/core directly — core is not a
117
- * declared dependency). Falls back to an always-undefined context.
166
+ * The navigation container ref for THIS app, whichever module instance
167
+ * provides it: @react-navigation/native's NavigationContainerRefContext
168
+ * (RN CLI, older expo-router) or Expo Router's vendored copy (SDK 54+).
169
+ * Returns undefined when rendered outside any NavigationContainer or when
170
+ * React Navigation is absent entirely.
118
171
  */
119
- export function getNavigationContainerRefContext() {
120
- return checkReactNavigationAvailability() && reactNavigation.NavigationContainerRefContext ? reactNavigation.NavigationContainerRefContext : FallbackNavigationContainerRefContext;
172
+ export function useSafeNavigationContainerRef() {
173
+ const [npmCtx, vendoredCtx] = getContainerRefContexts();
174
+ const npmRef = useContext(npmCtx);
175
+ const vendoredRef = useContext(vendoredCtx);
176
+ return npmRef ?? vendoredRef;
121
177
  }
122
178
 
123
179
  // ============================================================================