@buoy-gg/route-events 7.0.34 → 7.0.36

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 (30) 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 +4 -4
  7. package/lib/commonjs/components/RouteEventsTimeline.js +2 -2
  8. package/lib/commonjs/components/RoutesSitemap.js +16 -16
  9. package/lib/commonjs/sync/routeEventsSyncAdapter.js +34 -8
  10. package/lib/commonjs/useNavigationStack.js +52 -14
  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 +5 -5
  17. package/lib/module/components/RouteEventsTimeline.js +3 -3
  18. package/lib/module/components/RoutesSitemap.js +17 -17
  19. package/lib/module/sync/routeEventsSyncAdapter.js +34 -8
  20. package/lib/module/useNavigationStack.js +52 -15
  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/sync/routeEventsSyncAdapter.d.ts +9 -0
  27. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +1 -1
  28. package/lib/typescript/useNavigationStack.d.ts +8 -0
  29. package/lib/typescript/useNavigationStack.d.ts.map +1 -1
  30. package/package.json +5 -5
@@ -416,7 +416,7 @@ const styles = _reactNative.StyleSheet.create({
416
416
  loadingText: {
417
417
  color: _sharedUi.night.color.textSecondary,
418
418
  fontSize: 14,
419
- fontFamily: "monospace"
419
+ fontFamily: _sharedUi.monoFont
420
420
  },
421
421
  errorContainer: {
422
422
  flex: 1,
@@ -428,13 +428,13 @@ const styles = _reactNative.StyleSheet.create({
428
428
  color: _sharedUi.buoyColors.error,
429
429
  fontSize: 16,
430
430
  fontWeight: "600",
431
- fontFamily: "monospace",
431
+ fontFamily: _sharedUi.monoFont,
432
432
  marginBottom: 8
433
433
  },
434
434
  errorDetail: {
435
435
  color: _sharedUi.night.color.textSecondary,
436
436
  fontSize: 12,
437
- fontFamily: "monospace",
437
+ fontFamily: _sharedUi.monoFont,
438
438
  textAlign: "center"
439
439
  },
440
440
  emptyContainer: {
@@ -447,13 +447,13 @@ const styles = _reactNative.StyleSheet.create({
447
447
  color: _sharedUi.night.color.text,
448
448
  fontSize: 16,
449
449
  fontWeight: "600",
450
- fontFamily: "monospace",
450
+ fontFamily: _sharedUi.monoFont,
451
451
  marginBottom: 8
452
452
  },
453
453
  emptySubtext: {
454
454
  color: _sharedUi.night.color.textSecondary,
455
455
  fontSize: 14,
456
- fontFamily: "monospace",
456
+ fontFamily: _sharedUi.monoFont,
457
457
  textAlign: "center"
458
458
  },
459
459
  stackScroll: {
@@ -491,7 +491,7 @@ const styles = _reactNative.StyleSheet.create({
491
491
  fontSize: 13,
492
492
  fontWeight: "600",
493
493
  color: _sharedUi.night.color.text,
494
- fontFamily: "monospace",
494
+ fontFamily: _sharedUi.monoFont,
495
495
  flex: 1
496
496
  },
497
497
  copyRouteButton: {
@@ -507,7 +507,7 @@ const styles = _reactNative.StyleSheet.create({
507
507
  statusBadgeText: {
508
508
  fontSize: 10,
509
509
  fontWeight: "600",
510
- fontFamily: "monospace"
510
+ fontFamily: _sharedUi.monoFont
511
511
  },
512
512
  expandedContent: {
513
513
  paddingHorizontal: 12,
@@ -522,13 +522,13 @@ const styles = _reactNative.StyleSheet.create({
522
522
  detailLabel: {
523
523
  fontSize: 10,
524
524
  color: _sharedUi.night.color.textSecondary,
525
- fontFamily: "monospace",
525
+ fontFamily: _sharedUi.monoFont,
526
526
  marginBottom: 2
527
527
  },
528
528
  detailValue: {
529
529
  fontSize: 12,
530
530
  color: _sharedUi.night.color.text,
531
- fontFamily: "monospace"
531
+ fontFamily: _sharedUi.monoFont
532
532
  },
533
533
  dataViewerContainer: {
534
534
  marginTop: 4,
@@ -557,7 +557,7 @@ const styles = _reactNative.StyleSheet.create({
557
557
  fontSize: 12,
558
558
  fontWeight: "600",
559
559
  color: _sharedUi.night.color.text,
560
- fontFamily: "monospace"
560
+ fontFamily: _sharedUi.monoFont
561
561
  },
562
562
  actionButtonTextDisabled: {
563
563
  color: _sharedUi.night.color.textTertiary
@@ -565,7 +565,7 @@ const styles = _reactNative.StyleSheet.create({
565
565
  helpText: {
566
566
  fontSize: 9,
567
567
  color: _sharedUi.night.color.textSecondary,
568
- fontFamily: "monospace",
568
+ fontFamily: _sharedUi.monoFont,
569
569
  textAlign: "center",
570
570
  marginTop: 4,
571
571
  lineHeight: 12
@@ -550,12 +550,12 @@ const styles = _reactNative.StyleSheet.create({
550
550
  emptyText: {
551
551
  color: _sharedUi.night.color.textSecondary,
552
552
  fontSize: 14,
553
- fontFamily: "monospace"
553
+ fontFamily: _sharedUi.monoFont
554
554
  },
555
555
  emptySubtext: {
556
556
  color: _sharedUi.night.color.textTertiary,
557
557
  fontSize: 12,
558
- fontFamily: "monospace",
558
+ fontFamily: _sharedUi.monoFont,
559
559
  marginTop: 8
560
560
  },
561
561
  header: {
@@ -593,7 +593,7 @@ const styles = _reactNative.StyleSheet.create({
593
593
  actionLabel: {
594
594
  fontSize: 8,
595
595
  color: _sharedUi.night.color.textTertiary,
596
- fontFamily: "monospace",
596
+ fontFamily: _sharedUi.monoFont,
597
597
  textTransform: "uppercase",
598
598
  letterSpacing: 0.5
599
599
  },
@@ -620,13 +620,13 @@ const styles = _reactNative.StyleSheet.create({
620
620
  fontSize: 18,
621
621
  fontWeight: "800",
622
622
  color: _sharedUi.night.color.text,
623
- fontFamily: "monospace"
623
+ fontFamily: _sharedUi.monoFont
624
624
  },
625
625
  statLabel: {
626
626
  fontSize: 10,
627
627
  color: _sharedUi.night.color.textTertiary,
628
628
  marginTop: 4,
629
- fontFamily: "monospace",
629
+ fontFamily: _sharedUi.monoFont,
630
630
  textTransform: "uppercase",
631
631
  letterSpacing: 0.5
632
632
  },
@@ -642,7 +642,7 @@ const styles = _reactNative.StyleSheet.create({
642
642
  flex: 1,
643
643
  color: _sharedUi.night.color.text,
644
644
  fontSize: 14,
645
- fontFamily: "monospace"
645
+ fontFamily: _sharedUi.monoFont
646
646
  },
647
647
  clearButton: {
648
648
  padding: 4
@@ -659,7 +659,7 @@ const styles = _reactNative.StyleSheet.create({
659
659
  color: _sharedUi.night.color.accent,
660
660
  fontSize: 14,
661
661
  fontWeight: "600",
662
- fontFamily: "monospace"
662
+ fontFamily: _sharedUi.monoFont
663
663
  },
664
664
  scrollView: {
665
665
  flex: 1
@@ -681,7 +681,7 @@ const styles = _reactNative.StyleSheet.create({
681
681
  metaText: {
682
682
  fontSize: 12,
683
683
  color: _sharedUi.night.color.textSecondary,
684
- fontFamily: "monospace"
684
+ fontFamily: _sharedUi.monoFont
685
685
  },
686
686
  sourceBadge: {
687
687
  paddingHorizontal: 8,
@@ -695,7 +695,7 @@ const styles = _reactNative.StyleSheet.create({
695
695
  fontSize: 11,
696
696
  color: _sharedUi.night.color.textSecondary,
697
697
  textTransform: "capitalize",
698
- fontFamily: "monospace"
698
+ fontFamily: _sharedUi.monoFont
699
699
  },
700
700
  groupContainer: {
701
701
  marginVertical: 4
@@ -720,7 +720,7 @@ const styles = _reactNative.StyleSheet.create({
720
720
  fontSize: 12,
721
721
  fontWeight: "700",
722
722
  color: _sharedUi.night.color.text,
723
- fontFamily: "monospace",
723
+ fontFamily: _sharedUi.monoFont,
724
724
  textTransform: "uppercase",
725
725
  letterSpacing: 0.8
726
726
  },
@@ -739,7 +739,7 @@ const styles = _reactNative.StyleSheet.create({
739
739
  fontSize: 11,
740
740
  fontWeight: "600",
741
741
  color: "#3B82F6",
742
- fontFamily: "monospace"
742
+ fontFamily: _sharedUi.monoFont
743
743
  },
744
744
  screensList: {
745
745
  // Transparent so the night sheet's star field shows through.
@@ -756,7 +756,7 @@ const styles = _reactNative.StyleSheet.create({
756
756
  groupDescriptionText: {
757
757
  fontSize: 11,
758
758
  color: _sharedUi.night.color.textSecondary,
759
- fontFamily: "monospace",
759
+ fontFamily: _sharedUi.monoFont,
760
760
  lineHeight: 16
761
761
  },
762
762
  screenItem: {
@@ -795,7 +795,7 @@ const styles = _reactNative.StyleSheet.create({
795
795
  fontSize: 13,
796
796
  fontWeight: "600",
797
797
  color: _sharedUi.night.color.text,
798
- fontFamily: "monospace",
798
+ fontFamily: _sharedUi.monoFont,
799
799
  flex: 1
800
800
  },
801
801
  screenHeaderActions: {
@@ -812,7 +812,7 @@ const styles = _reactNative.StyleSheet.create({
812
812
  statusText: {
813
813
  fontSize: 10,
814
814
  fontWeight: "600",
815
- fontFamily: "monospace"
815
+ fontFamily: _sharedUi.monoFont
816
816
  },
817
817
  headerActionButton: {
818
818
  padding: 6,
@@ -830,7 +830,7 @@ const styles = _reactNative.StyleSheet.create({
830
830
  navigateButtonText: {
831
831
  fontSize: 12,
832
832
  color: _sharedUi.night.color.accent,
833
- fontFamily: "monospace",
833
+ fontFamily: _sharedUi.monoFont,
834
834
  fontWeight: "600"
835
835
  }
836
836
  });
@@ -90,7 +90,7 @@ const styles = _reactNative.StyleSheet.create({
90
90
  fontWeight: "600",
91
91
  // Ink on a filled accent surface — the night accent takes dark ink.
92
92
  color: _sharedUi.night.color.onAccent,
93
- fontFamily: "monospace",
93
+ fontFamily: _sharedUi.monoFont,
94
94
  textTransform: "uppercase",
95
95
  letterSpacing: 0.5
96
96
  }
@@ -172,14 +172,14 @@ const styles = _reactNative.StyleSheet.create({
172
172
  detailLabel: {
173
173
  fontSize: 10,
174
174
  color: _sharedUi.night.color.textSecondary,
175
- fontFamily: "monospace",
175
+ fontFamily: _sharedUi.monoFont,
176
176
  fontWeight: "600",
177
177
  minWidth: 70
178
178
  },
179
179
  detailValue: {
180
180
  fontSize: 11,
181
181
  color: _sharedUi.night.color.text,
182
- fontFamily: "monospace",
182
+ fontFamily: _sharedUi.monoFont,
183
183
  flex: 1
184
184
  },
185
185
  copyButton: {
@@ -198,7 +198,7 @@ const styles = _reactNative.StyleSheet.create({
198
198
  dataViewerTitle: {
199
199
  fontSize: 11,
200
200
  color: _sharedUi.night.color.textSecondary,
201
- fontFamily: "monospace",
201
+ fontFamily: _sharedUi.monoFont,
202
202
  fontWeight: "600"
203
203
  }
204
204
  });
@@ -191,7 +191,7 @@ const styles = _reactNative.StyleSheet.create({
191
191
  badgeText: {
192
192
  fontSize: 11,
193
193
  fontWeight: "600",
194
- fontFamily: "monospace"
194
+ fontFamily: _sharedUi.monoFont
195
195
  },
196
196
  goButton: {
197
197
  backgroundColor: _sharedUi.night.color.accent,
@@ -204,6 +204,6 @@ const styles = _reactNative.StyleSheet.create({
204
204
  fontWeight: "600",
205
205
  // Ink on a filled accent surface — the night accent takes dark ink.
206
206
  color: _sharedUi.night.color.onAccent,
207
- fontFamily: "monospace"
207
+ fontFamily: _sharedUi.monoFont
208
208
  }
209
209
  });
@@ -646,7 +646,7 @@ const styles = _reactNative.StyleSheet.create({
646
646
  fontWeight: "600",
647
647
  marginTop: 16,
648
648
  marginBottom: 8,
649
- fontFamily: "monospace",
649
+ fontFamily: _sharedUi.monoFont,
650
650
  letterSpacing: 0.5,
651
651
  textTransform: "uppercase"
652
652
  },
@@ -654,7 +654,7 @@ const styles = _reactNative.StyleSheet.create({
654
654
  color: _sharedUi.night.color.textSecondary,
655
655
  fontSize: 14,
656
656
  textAlign: "center",
657
- fontFamily: "monospace"
657
+ fontFamily: _sharedUi.monoFont
658
658
  },
659
659
  contentWrapper: {
660
660
  flex: 1
@@ -669,7 +669,7 @@ const styles = _reactNative.StyleSheet.create({
669
669
  statsText: {
670
670
  fontSize: 11,
671
671
  color: _sharedUi.night.color.textSecondary,
672
- fontFamily: "monospace",
672
+ fontFamily: _sharedUi.monoFont,
673
673
  textAlign: "center"
674
674
  },
675
675
  statsWarning: {
@@ -699,7 +699,7 @@ const styles = _reactNative.StyleSheet.create({
699
699
  fontSize: 12,
700
700
  fontWeight: "600",
701
701
  color: _sharedUi.night.color.warning,
702
- fontFamily: "monospace"
702
+ fontFamily: _sharedUi.monoFont
703
703
  },
704
704
  lockedEventsBannerSubtext: {
705
705
  fontSize: 11,
@@ -94,7 +94,7 @@ const styles = _reactNative.StyleSheet.create({
94
94
  fontWeight: "600",
95
95
  marginTop: 16,
96
96
  marginBottom: 8,
97
- fontFamily: "monospace",
97
+ fontFamily: _sharedUi.monoFont,
98
98
  letterSpacing: 0.5,
99
99
  textTransform: "uppercase"
100
100
  },
@@ -102,6 +102,6 @@ const styles = _reactNative.StyleSheet.create({
102
102
  color: _sharedUi.night.color.textSecondary,
103
103
  fontSize: 14,
104
104
  textAlign: "center",
105
- fontFamily: "monospace"
105
+ fontFamily: _sharedUi.monoFont
106
106
  }
107
107
  });
@@ -687,7 +687,7 @@ const styles = _reactNative.StyleSheet.create({
687
687
  loadingText: {
688
688
  color: _sharedUi.night.color.textSecondary,
689
689
  fontSize: 14,
690
- fontFamily: "monospace",
690
+ fontFamily: _sharedUi.monoFont,
691
691
  textAlign: "center",
692
692
  lineHeight: 20,
693
693
  paddingHorizontal: 32,
@@ -728,13 +728,13 @@ const styles = _reactNative.StyleSheet.create({
728
728
  fontSize: 18,
729
729
  fontWeight: "800",
730
730
  color: _sharedUi.night.color.text,
731
- fontFamily: "monospace"
731
+ fontFamily: _sharedUi.monoFont
732
732
  },
733
733
  statLabel: {
734
734
  fontSize: 10,
735
735
  color: _sharedUi.night.color.textTertiary,
736
736
  marginTop: 4,
737
- fontFamily: "monospace",
737
+ fontFamily: _sharedUi.monoFont,
738
738
  textTransform: "uppercase",
739
739
  letterSpacing: 0.5
740
740
  },
@@ -773,7 +773,7 @@ const styles = _reactNative.StyleSheet.create({
773
773
  actionLabel: {
774
774
  fontSize: 8,
775
775
  color: _sharedUi.night.color.textTertiary,
776
- fontFamily: "monospace",
776
+ fontFamily: _sharedUi.monoFont,
777
777
  textTransform: "uppercase",
778
778
  letterSpacing: 0.5
779
779
  },
@@ -797,7 +797,7 @@ const styles = _reactNative.StyleSheet.create({
797
797
  metaText: {
798
798
  fontSize: 12,
799
799
  color: _sharedUi.night.color.textSecondary,
800
- fontFamily: "monospace"
800
+ fontFamily: _sharedUi.monoFont
801
801
  },
802
802
  sourceBadge: {
803
803
  paddingHorizontal: 8,
@@ -811,7 +811,7 @@ const styles = _reactNative.StyleSheet.create({
811
811
  fontSize: 11,
812
812
  color: _sharedUi.night.color.textSecondary,
813
813
  textTransform: "capitalize",
814
- fontFamily: "monospace"
814
+ fontFamily: _sharedUi.monoFont
815
815
  },
816
816
  searchContainer: {
817
817
  flexDirection: "row",
@@ -825,7 +825,7 @@ const styles = _reactNative.StyleSheet.create({
825
825
  flex: 1,
826
826
  color: _sharedUi.night.color.text,
827
827
  fontSize: 14,
828
- fontFamily: "monospace"
828
+ fontFamily: _sharedUi.monoFont
829
829
  },
830
830
  clearButton: {
831
831
  padding: 4
@@ -842,7 +842,7 @@ const styles = _reactNative.StyleSheet.create({
842
842
  color: _sharedUi.night.color.accent,
843
843
  fontSize: 14,
844
844
  fontWeight: "600",
845
- fontFamily: "monospace"
845
+ fontFamily: _sharedUi.monoFont
846
846
  },
847
847
  scrollView: {
848
848
  flex: 1
@@ -857,7 +857,7 @@ const styles = _reactNative.StyleSheet.create({
857
857
  emptyText: {
858
858
  color: _sharedUi.night.color.textSecondary,
859
859
  fontSize: 14,
860
- fontFamily: "monospace"
860
+ fontFamily: _sharedUi.monoFont
861
861
  },
862
862
  groupContainer: {
863
863
  marginVertical: 4
@@ -882,7 +882,7 @@ const styles = _reactNative.StyleSheet.create({
882
882
  fontSize: 12,
883
883
  fontWeight: "700",
884
884
  color: _sharedUi.night.color.text,
885
- fontFamily: "monospace",
885
+ fontFamily: _sharedUi.monoFont,
886
886
  textTransform: "uppercase",
887
887
  letterSpacing: 0.8
888
888
  },
@@ -901,7 +901,7 @@ const styles = _reactNative.StyleSheet.create({
901
901
  fontSize: 11,
902
902
  fontWeight: "600",
903
903
  color: "#3B82F6",
904
- fontFamily: "monospace"
904
+ fontFamily: _sharedUi.monoFont
905
905
  },
906
906
  routesList: {
907
907
  // Transparent so the night sheet's star field shows through.
@@ -918,7 +918,7 @@ const styles = _reactNative.StyleSheet.create({
918
918
  groupDescriptionText: {
919
919
  fontSize: 11,
920
920
  color: _sharedUi.night.color.textSecondary,
921
- fontFamily: "monospace",
921
+ fontFamily: _sharedUi.monoFont,
922
922
  lineHeight: 16
923
923
  },
924
924
  routeItem: {
@@ -961,7 +961,7 @@ const styles = _reactNative.StyleSheet.create({
961
961
  fontSize: 13,
962
962
  fontWeight: "600",
963
963
  color: _sharedUi.night.color.text,
964
- fontFamily: "monospace",
964
+ fontFamily: _sharedUi.monoFont,
965
965
  flex: 1
966
966
  },
967
967
  routeHeaderActions: {
@@ -984,7 +984,7 @@ const styles = _reactNative.StyleSheet.create({
984
984
  fontSize: 11,
985
985
  fontWeight: "600",
986
986
  color: _sharedUi.night.color.textSecondary,
987
- fontFamily: "monospace"
987
+ fontFamily: _sharedUi.monoFont
988
988
  },
989
989
  typeTag: {
990
990
  borderRadius: 4,
@@ -995,7 +995,7 @@ const styles = _reactNative.StyleSheet.create({
995
995
  typeText: {
996
996
  fontSize: 10,
997
997
  fontWeight: "600",
998
- fontFamily: "monospace"
998
+ fontFamily: _sharedUi.monoFont
999
999
  },
1000
1000
  // Compact icon-sized Copy button in the header action cluster.
1001
1001
  iconAction: {
@@ -1022,7 +1022,7 @@ const styles = _reactNative.StyleSheet.create({
1022
1022
  goButtonText: {
1023
1023
  fontSize: 12,
1024
1024
  color: _sharedUi.night.color.accent,
1025
- fontFamily: "monospace",
1025
+ fontFamily: _sharedUi.monoFont,
1026
1026
  fontWeight: "600"
1027
1027
  },
1028
1028
  childrenContainer: {
@@ -131,7 +131,21 @@ const routeEventsSyncAdapter = exports.routeEventsSyncAdapter = {
131
131
  // BaseEventStore is newest-FIRST, and the store only captures while
132
132
  // something is subscribed.
133
133
  const latest = _routeEventStore.routeEventStore.getEvents()[0];
134
- if (latest) {
134
+
135
+ /**
136
+ * A RECORDED event only wins while the recorder is running.
137
+ *
138
+ * The store starts capturing when its first subscriber arrives and stops
139
+ * with the last one, so an app with Ask Buoy on the phone and no
140
+ * dashboard attached has NO route events at all — the stack fallback
141
+ * below is its normal path, not the fresh-launch edge case this code was
142
+ * written for. Worse, events left over from a closed capture window are
143
+ * stale the moment the user navigates again: a live QA session read
144
+ * `/(tabs)/menu` from an event six scenarios after the app had moved to
145
+ * `/cart`. The live stack cannot be stale, so it wins whenever nothing
146
+ * is recording.
147
+ */
148
+ if (latest && _routeEventStore.routeEventStore.isCapturing()) {
135
149
  return {
136
150
  path: latest.pathname,
137
151
  params: toWireParams(latest.params),
@@ -139,13 +153,12 @@ const routeEventsSyncAdapter = exports.routeEventsSyncAdapter = {
139
153
  source: "event"
140
154
  };
141
155
  }
142
- // Nothing has navigated yet — which is exactly the state a freshly
143
- // launched app is in, and exactly when a macro recorder needs to know
144
- // where it is starting from. The live stack knows even when the history
145
- // is empty, so falling back to it is the difference between a macro that
146
- // can run from anywhere and one that only works if you happen to be on
147
- // the screen it was recorded from.
148
- const focused = _navigationStackStore.navigationStackStore.getStack().find(item => item.isFocused) ?? _navigationStackStore.navigationStackStore.getStack().slice(-1)[0];
156
+
157
+ // The live stack knows even when the history is empty, which is the
158
+ // difference between a macro that can run from anywhere and one that
159
+ // only works if you happen to be on the screen it was recorded from.
160
+ const stack = _navigationStackStore.navigationStackStore.getStack();
161
+ const focused = stack.find(item => item.isFocused) ?? stack[stack.length - 1];
149
162
  if (focused?.pathname) {
150
163
  return {
151
164
  path: focused.pathname,
@@ -154,6 +167,19 @@ const routeEventsSyncAdapter = exports.routeEventsSyncAdapter = {
154
167
  source: "stack"
155
168
  };
156
169
  }
170
+
171
+ // No live stack (no <RouteTracker/>) and the recorder is off: an old
172
+ // event is all there is. Say that it may be stale rather than passing it
173
+ // off as where the app is.
174
+ if (latest) {
175
+ return {
176
+ path: latest.pathname,
177
+ params: toWireParams(latest.params),
178
+ at: latest.timestamp,
179
+ source: "event",
180
+ stale: true
181
+ };
182
+ }
157
183
  return {
158
184
  path: null,
159
185
  params: {},
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.buildStack = buildStack;
6
7
  exports.useNavigationStack = useNavigationStack;
7
8
  var _react = require("react");
8
9
  var _safeReactNavigation = require("./utils/safeReactNavigation");
@@ -85,14 +86,39 @@ function buildNestedNavigateAction(path) {
85
86
  // ============================================================================
86
87
 
87
88
  /**
88
- * Recursively collect all routes from the navigation state tree
89
+ * Recursively collect the routes from the navigation state tree.
90
+ *
91
+ * FOCUS COMES FROM `state.index`, NEVER FROM POSITION. React Navigation names
92
+ * the focused route by index within `routes`; the array's order is declaration
93
+ * order. `buildStack` used to equate the two — "the last route in the collected
94
+ * list is the focused one" — and a hidden tab declared last (`href: null` on an
95
+ * expo-router `Tabs.Screen`) then became the reported current screen for the
96
+ * whole session. Measured on a real app: Home and Rewards both reported as a
97
+ * hidden Menu tab, in every turn of a 55-scenario QA run, because `menu` was
98
+ * declared last.
99
+ *
100
+ * TAB AND DRAWER SIBLINGS ARE NOT A STACK. They are alternative destinations
101
+ * that happen to be mounted at the same time; `canPop` and `popToIndex` never
102
+ * meant anything for them. Only the focused branch of a tab/drawer is
103
+ * collected, so what comes back is the stack you could actually pop.
89
104
  */
90
- function collectRoutesFromState(state, depth = 0, parentPath = "") {
91
- if (!state || !state.routes) return [];
105
+ function collectRoutesFromState(state, depth = 0, parentPath = "", onFocusedChain = true) {
106
+ if (!state || !Array.isArray(state.routes) || state.routes.length === 0) {
107
+ return [];
108
+ }
92
109
  const routes = [];
110
+
111
+ // An absent or out-of-range `index` is incomplete state (mid-rehydration, a
112
+ // partially built child). Fall back to the last route — a stack's top — and
113
+ // never to an arbitrary sibling.
114
+ const declaredIndex = typeof state.index === "number" ? state.index : NaN;
115
+ const focusedIndex = Number.isInteger(declaredIndex) && declaredIndex >= 0 && declaredIndex < state.routes.length ? declaredIndex : state.routes.length - 1;
116
+ const navigatorType = typeof state.type === "string" ? state.type : undefined;
117
+ const siblingsAreStack = navigatorType !== "tab" && navigatorType !== "drawer";
93
118
  for (let i = 0; i < state.routes.length; i++) {
119
+ if (!siblingsAreStack && i !== focusedIndex) continue;
94
120
  const route = state.routes[i];
95
- const isFocused = i === state.index;
121
+ const isFocused = i === focusedIndex;
96
122
 
97
123
  // Build the path
98
124
  let pathname = route.name;
@@ -116,25 +142,30 @@ function collectRoutesFromState(state, depth = 0, parentPath = "") {
116
142
  });
117
143
  }
118
144
  }
145
+
146
+ // Descend the focused branch first: whatever it yields owns the focus, and
147
+ // this route only keeps it when nothing deeper claimed it.
148
+ const onChain = onFocusedChain && isFocused;
149
+ const nestedRoutes = onChain && route.state ? collectRoutesFromState(route.state, depth + 1, pathname, true) : [];
119
150
  routes.push({
120
151
  key: route.key,
121
152
  name: route.name,
122
153
  path: pathname,
123
154
  params: route.params || {},
124
- state: route.state
155
+ state: route.state,
156
+ isFocused: onChain && nestedRoutes.length === 0
125
157
  });
126
-
127
- // If this route has nested state and is focused, recurse
128
- if (isFocused && route.state) {
129
- const nestedRoutes = collectRoutesFromState(route.state, depth + 1, pathname);
130
- routes.push(...nestedRoutes);
131
- }
158
+ routes.push(...nestedRoutes);
132
159
  }
133
160
  return routes;
134
161
  }
135
162
 
136
163
  /**
137
- * Transform raw navigation state into display items
164
+ * Transform raw navigation state into display items.
165
+ *
166
+ * Exported for tests: the focus rule below is the one thing every route answer
167
+ * in the product rests on, and it was previously only reachable by extracting
168
+ * the function from source with the TypeScript AST.
138
169
  */
139
170
  function buildStack(navigationState, isExpo) {
140
171
  if (!navigationState) return [];
@@ -153,7 +184,14 @@ function buildStack(navigationState, isExpo) {
153
184
  // If we filtered everything out, just keep the last route
154
185
  const finalRoutes = filteredRoutes.length > 0 ? filteredRoutes : routes.slice(-1);
155
186
 
156
- // The last route in the collected list is the focused one
187
+ /**
188
+ * Focus survives filtering. It is decided in `collectRoutesFromState` from
189
+ * `state.index`; the only case that falls back to the last item is one
190
+ * where the filter dropped the focused route itself (an internal expo
191
+ * layout), which is the one situation the old rule was ever right for.
192
+ */
193
+ const collectedFocus = finalRoutes.findIndex(route => route.isFocused);
194
+ const focusIndex = collectedFocus >= 0 ? collectedFocus : finalRoutes.length - 1;
157
195
  return finalRoutes.map((route, index) => {
158
196
  let cleanPath = route.path || `/${route.name}`;
159
197
  // Clean up Expo Router internal /__root prefix
@@ -169,7 +207,7 @@ function buildStack(navigationState, isExpo) {
169
207
  name: route.name,
170
208
  pathname: cleanPath,
171
209
  params: route.params || {},
172
- isFocused: index === finalRoutes.length - 1,
210
+ isFocused: index === focusIndex,
173
211
  index,
174
212
  canPop: index > 0
175
213
  };