@buoy-gg/route-events 3.0.1 → 4.0.1

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 (35) hide show
  1. package/lib/commonjs/RouteTracker.js +38 -4
  2. package/lib/commonjs/components/NavigationStack.js +105 -124
  3. package/lib/commonjs/components/RouteEventsModalWithTabs.js +11 -4
  4. package/lib/commonjs/components/RoutesSitemap.js +205 -169
  5. package/lib/commonjs/expoRouterStore.js +17 -0
  6. package/lib/commonjs/index.js +7 -0
  7. package/lib/commonjs/stores/navigationStackStore.js +45 -0
  8. package/lib/commonjs/sync/routeEventsSyncAdapter.js +148 -0
  9. package/lib/commonjs/useRouteSitemap.js +28 -7
  10. package/lib/module/RouteTracker.js +39 -5
  11. package/lib/module/components/NavigationStack.js +106 -125
  12. package/lib/module/components/RouteEventsModalWithTabs.js +11 -4
  13. package/lib/module/components/RoutesSitemap.js +207 -171
  14. package/lib/module/expoRouterStore.js +16 -0
  15. package/lib/module/index.js +4 -0
  16. package/lib/module/stores/navigationStackStore.js +41 -0
  17. package/lib/module/sync/routeEventsSyncAdapter.js +145 -0
  18. package/lib/module/useRouteSitemap.js +29 -8
  19. package/lib/typescript/RouteTracker.d.ts.map +1 -1
  20. package/lib/typescript/components/NavigationStack.d.ts +24 -1
  21. package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
  22. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +1 -1
  23. package/lib/typescript/components/RoutesSitemap.d.ts +19 -1
  24. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -1
  25. package/lib/typescript/expoRouterStore.d.ts +8 -0
  26. package/lib/typescript/expoRouterStore.d.ts.map +1 -1
  27. package/lib/typescript/index.d.ts +3 -1
  28. package/lib/typescript/index.d.ts.map +1 -1
  29. package/lib/typescript/stores/navigationStackStore.d.ts +33 -0
  30. package/lib/typescript/stores/navigationStackStore.d.ts.map +1 -0
  31. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts +69 -0
  32. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +1 -0
  33. package/lib/typescript/useRouteSitemap.d.ts +17 -0
  34. package/lib/typescript/useRouteSitemap.d.ts.map +1 -1
  35. package/package.json +6 -6
@@ -25,12 +25,35 @@ var _jsxRuntime = require("react/jsx-runtime");
25
25
  // Types
26
26
  // ============================================================================
27
27
 
28
+ // On desktop (web) the toolbar has far more room, so scale the action buttons
29
+ // up for easier clicking and a less cramped header.
30
+ const IS_DESKTOP = _reactNative.Platform.OS === "web";
31
+ const ACTION_ICON_SIZE = IS_DESKTOP ? 22 : 16;
32
+
33
+ // Synthetic route representing the app's home/index ("/"). Used by the
34
+ // "Home" toolbar shortcut so navigation works the same as tapping any route.
35
+ const HOME_ROUTE = {
36
+ path: "/",
37
+ name: "index",
38
+ type: "index",
39
+ params: [],
40
+ nodeType: "route",
41
+ contextKey: "/",
42
+ isInternal: false,
43
+ children: [],
44
+ depth: 0
45
+ };
46
+
28
47
  // ============================================================================
29
48
  // Main Component
30
49
  // ============================================================================
31
50
 
32
51
  function RoutesSitemap({
33
- style
52
+ style,
53
+ injectedRoutes,
54
+ injectedSource,
55
+ injectedLastUpdatedAt,
56
+ onNavigateRoute
34
57
  }) {
35
58
  const [searchQuery, setSearchQuery] = (0, _react.useState)("");
36
59
  const [isSearching, setIsSearching] = (0, _react.useState)(false);
@@ -52,6 +75,7 @@ function RoutesSitemap({
52
75
  groups,
53
76
  stats,
54
77
  isLoaded,
78
+ isSupported,
55
79
  filteredRoutes,
56
80
  routes,
57
81
  refresh,
@@ -59,7 +83,10 @@ function RoutesSitemap({
59
83
  source
60
84
  } = (0, _useRouteSitemap.useRouteSitemap)({
61
85
  searchQuery,
62
- sortBy: "path"
86
+ sortBy: "path",
87
+ injectedRoutes,
88
+ injectedSource,
89
+ injectedLastUpdatedAt
63
90
  });
64
91
 
65
92
  // Prepare copy data - memoized so it only rebuilds when dependencies change
@@ -146,9 +173,16 @@ function RoutesSitemap({
146
173
  }
147
174
  }], "plain-text");
148
175
  }, [router]);
149
- const handleNavigate = (0, _react.useCallback)(route => {
150
- // Gate behind Pro
151
- if (!isPro) {
176
+ const handleNavigate = (0, _react.useCallback)((route, options) => {
177
+ // When a navigation delegate is supplied (dashboard → device), hand off
178
+ // entirely: the delegate owns Pro gating and param resolution.
179
+ if (onNavigateRoute) {
180
+ onNavigateRoute(route);
181
+ return;
182
+ }
183
+
184
+ // Gate behind Pro (the Home shortcut bypasses this — it's always free)
185
+ if (!isPro && !options?.bypassPro) {
152
186
  setShowUpgradeModal(true);
153
187
  return;
154
188
  }
@@ -182,7 +216,12 @@ function RoutesSitemap({
182
216
  } catch (error) {
183
217
  _reactNative.Alert.alert("Navigation Error", String(error));
184
218
  }
185
- }, [router, promptForParams, isPro]);
219
+ }, [router, promptForParams, isPro, onNavigateRoute]);
220
+ const handleGoHome = (0, _react.useCallback)(() => {
221
+ handleNavigate(HOME_ROUTE, {
222
+ bypassPro: true
223
+ });
224
+ }, [handleNavigate]);
186
225
  const handleManualRefresh = (0, _react.useCallback)(() => {
187
226
  if (isRefreshing) return;
188
227
  setIsRefreshing(true);
@@ -217,7 +256,7 @@ function RoutesSitemap({
217
256
  style: styles.loadingContainer,
218
257
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
219
258
  style: styles.loadingText,
220
- children: "Loading routes..."
259
+ children: isSupported ? "Loading routes..." : "Route sitemap isn't available on the dashboard — the route tree lives on the device. Use the Events tab to see navigation here."
221
260
  })
222
261
  })
223
262
  });
@@ -229,39 +268,54 @@ function RoutesSitemap({
229
268
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
230
269
  style: styles.actionsRow,
231
270
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
232
- style: styles.actionWrapper,
233
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
271
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
272
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
273
+ style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
274
+ onPress: handleGoHome,
275
+ accessibilityLabel: "Go to home route",
276
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Home, {
277
+ size: ACTION_ICON_SIZE,
278
+ color: _sharedUi.buoyColors.textSecondary
279
+ })
280
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
281
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
282
+ children: "Home"
283
+ })]
284
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
285
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
286
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton, {
234
287
  value: copyAllData,
235
- buttonStyle: styles.actionButtonHeader
288
+ size: IS_DESKTOP ? ACTION_ICON_SIZE : 14,
289
+ buttonStyle: IS_DESKTOP ? styles.actionButtonHeaderDesktop : styles.actionButtonHeader
236
290
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
237
- style: styles.actionLabel,
291
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
238
292
  children: "Copy"
239
293
  })]
240
294
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
241
- style: styles.actionWrapper,
295
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
242
296
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
243
- style: [styles.iconButton, isRefreshing && styles.refreshButtonDisabled],
297
+ style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop, isRefreshing && styles.refreshButtonDisabled],
244
298
  onPress: handleManualRefresh,
245
299
  disabled: isRefreshing,
246
300
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
247
- size: 16,
301
+ size: ACTION_ICON_SIZE,
248
302
  color: isRefreshing ? _sharedUi.buoyColors.textMuted : _sharedUi.buoyColors.textSecondary
249
303
  })
250
304
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
251
- style: styles.actionLabel,
305
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
252
306
  children: "Refresh"
253
307
  })]
254
308
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
255
- style: styles.actionWrapper,
309
+ style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
256
310
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
257
- style: styles.iconButton,
311
+ style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
258
312
  onPress: () => setIsSearching(true),
259
313
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
260
- size: 16,
314
+ size: ACTION_ICON_SIZE,
261
315
  color: _sharedUi.buoyColors.textSecondary
262
316
  })
263
317
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
264
- style: styles.actionLabel,
318
+ style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
265
319
  children: "Search"
266
320
  })]
267
321
  })]
@@ -407,18 +461,18 @@ function RouteGroupView({
407
461
  children: group.routes.length
408
462
  })
409
463
  })]
410
- }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
464
+ }), group.description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
465
+ style: styles.groupDescription,
466
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
467
+ style: styles.groupDescriptionText,
468
+ children: group.description
469
+ })
470
+ }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
411
471
  style: styles.routesList,
412
- children: [group.description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
413
- style: styles.groupDescription,
414
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
415
- style: styles.groupDescriptionText,
416
- children: group.description
417
- })
418
- }), group.routes.map((route, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteItemView, {
472
+ children: group.routes.map((route, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteItemView, {
419
473
  route: route,
420
474
  onNavigate: onNavigate
421
- }, `${route.path}-${index}`))]
475
+ }, `${route.path}-${index}`))
422
476
  })]
423
477
  });
424
478
  }
@@ -429,85 +483,85 @@ function RouteItemView({
429
483
  }) {
430
484
  const [isExpanded, setIsExpanded] = (0, _react.useState)(false);
431
485
  const hasChildren = route.children.length > 0;
432
- const hasParams = route.params.length > 0;
433
486
  const typeColor = getRouteTypeColor(route.type);
434
487
  const canNavigate = route.type !== "layout" && route.type !== "group";
435
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
488
+
489
+ // Leaf routes (no children) have nothing to drill into — expanding would only
490
+ // reveal the Copy/Go actions, so show those inline instead of behind a toggle.
491
+ const isExpandable = hasChildren;
492
+ const showDetails = isExpandable ? isExpanded : true;
493
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
436
494
  style: [styles.routeItem, {
437
495
  marginLeft: depth * 12
438
496
  }],
439
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
497
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
440
498
  style: styles.routeCard,
441
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
442
- style: styles.routeHeaderLeft,
443
- onPress: () => setIsExpanded(!isExpanded),
444
- activeOpacity: 0.7,
445
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
446
- style: styles.expandIndicator,
447
- children: isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
448
- size: 14,
449
- color: _sharedUi.buoyColors.textSecondary
450
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
451
- size: 14,
452
- color: _sharedUi.buoyColors.textSecondary
453
- })
454
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
455
- style: styles.routePath,
456
- numberOfLines: 1,
457
- children: route.path
458
- })]
459
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
460
- style: styles.routeHeaderActions,
461
- children: [hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
462
- style: styles.childCountBadge,
463
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
464
- style: styles.childCountText,
465
- children: route.children.length
466
- })
467
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
468
- style: [styles.typeTag, {
469
- backgroundColor: `${typeColor}15`,
470
- borderColor: `${typeColor}40`
471
- }],
472
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
473
- style: [styles.typeText, {
474
- color: typeColor
475
- }],
476
- children: route.type.toUpperCase()
477
- })
478
- })]
479
- })]
480
- }), isExpanded && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
481
- style: styles.routeDetails,
482
499
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
483
- style: styles.routeButtons,
484
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton, {
485
- value: route.path,
486
- buttonStyle: styles.actionButton
487
- }), canNavigate && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
488
- style: [styles.actionButton, styles.navigateButton],
489
- onPress: () => onNavigate(route),
490
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
491
- style: styles.navigateButtonText,
492
- children: "Go"
493
- })
494
- })]
495
- }), hasParams && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
496
- style: styles.paramsContainer,
497
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
498
- style: styles.paramsLabel,
499
- children: "Parameters:"
500
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
501
- style: styles.paramsRow,
502
- children: route.params.map(param => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
503
- style: styles.paramTag,
500
+ style: styles.routeHeader,
501
+ children: [isExpandable ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
502
+ style: styles.routeHeaderLeft,
503
+ onPress: () => setIsExpanded(!isExpanded),
504
+ activeOpacity: 0.7,
505
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
506
+ style: styles.expandIndicator,
507
+ children: isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
508
+ size: 14,
509
+ color: _sharedUi.buoyColors.textSecondary
510
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
511
+ size: 14,
512
+ color: _sharedUi.buoyColors.textSecondary
513
+ })
514
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
515
+ style: styles.routePath,
516
+ numberOfLines: 3,
517
+ children: route.path
518
+ })]
519
+ }) :
520
+ /*#__PURE__*/
521
+ // Non-toggling header for leaf routes (keeps the chevron column as a
522
+ // spacer so paths stay aligned with expandable siblings).
523
+ (0, _jsxRuntime.jsxs)(_reactNative.View, {
524
+ style: styles.routeHeaderLeft,
525
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
526
+ style: styles.expandIndicator
527
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
528
+ style: styles.routePath,
529
+ numberOfLines: 3,
530
+ children: route.path
531
+ })]
532
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
533
+ style: styles.routeHeaderActions,
534
+ children: [hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
535
+ style: styles.childCountBadge,
504
536
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
505
- style: styles.paramText,
506
- children: param
537
+ style: styles.childCountText,
538
+ children: route.children.length
507
539
  })
508
- }, param))
540
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
541
+ style: [styles.typeTag, {
542
+ backgroundColor: `${typeColor}15`,
543
+ borderColor: `${typeColor}40`
544
+ }],
545
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
546
+ style: [styles.typeText, {
547
+ color: typeColor
548
+ }],
549
+ children: route.type.toUpperCase()
550
+ })
551
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton, {
552
+ value: route.path,
553
+ buttonStyle: styles.iconAction
554
+ }), canNavigate && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
555
+ style: styles.goButton,
556
+ onPress: () => onNavigate(route),
557
+ activeOpacity: 0.7,
558
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
559
+ style: styles.goButtonText,
560
+ children: "Go"
561
+ })
562
+ })]
509
563
  })]
510
- }), hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
564
+ }), showDetails && hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
511
565
  style: styles.childrenContainer,
512
566
  children: route.children.map((child, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteItemView, {
513
567
  route: child,
@@ -515,7 +569,7 @@ function RouteItemView({
515
569
  onNavigate: onNavigate
516
570
  }, `${child.path}-${index}`))
517
571
  })]
518
- })]
572
+ })
519
573
  });
520
574
  }
521
575
 
@@ -569,7 +623,11 @@ const styles = _reactNative.StyleSheet.create({
569
623
  loadingText: {
570
624
  color: _sharedUi.buoyColors.textSecondary,
571
625
  fontSize: 14,
572
- fontFamily: "monospace"
626
+ fontFamily: "monospace",
627
+ textAlign: "center",
628
+ lineHeight: 20,
629
+ paddingHorizontal: 32,
630
+ maxWidth: 420
573
631
  },
574
632
  header: {
575
633
  flexDirection: "column",
@@ -581,7 +639,7 @@ const styles = _reactNative.StyleSheet.create({
581
639
  actionsRow: {
582
640
  flexDirection: "row",
583
641
  alignItems: "center",
584
- justifyContent: "flex-end",
642
+ justifyContent: "space-between",
585
643
  gap: 12,
586
644
  paddingBottom: 4
587
645
  },
@@ -616,22 +674,37 @@ const styles = _reactNative.StyleSheet.create({
616
674
  letterSpacing: 0.5
617
675
  },
618
676
  actionWrapper: {
677
+ flex: 1,
619
678
  alignItems: "center",
620
679
  justifyContent: "center",
621
680
  gap: 4,
622
681
  minWidth: 48
623
682
  },
683
+ actionWrapperDesktop: {
684
+ gap: 8,
685
+ minWidth: 72
686
+ },
624
687
  actionButtonHeader: {
625
688
  padding: 6,
626
689
  borderRadius: 4,
627
690
  backgroundColor: _sharedUi.buoyColors.input
628
691
  },
692
+ actionButtonHeaderDesktop: {
693
+ padding: 12,
694
+ borderRadius: 8,
695
+ backgroundColor: _sharedUi.buoyColors.input
696
+ },
629
697
  iconButton: {
630
698
  padding: 6,
631
699
  borderRadius: 4,
632
700
  alignItems: "center",
633
701
  justifyContent: "center"
634
702
  },
703
+ iconButtonDesktop: {
704
+ padding: 12,
705
+ borderRadius: 8,
706
+ backgroundColor: _sharedUi.buoyColors.input
707
+ },
635
708
  actionLabel: {
636
709
  fontSize: 8,
637
710
  color: _sharedUi.buoyColors.textMuted,
@@ -639,6 +712,9 @@ const styles = _reactNative.StyleSheet.create({
639
712
  textTransform: "uppercase",
640
713
  letterSpacing: 0.5
641
714
  },
715
+ actionLabelDesktop: {
716
+ fontSize: 12
717
+ },
642
718
  refreshButtonDisabled: {
643
719
  opacity: 0.5
644
720
  },
@@ -783,15 +859,11 @@ const styles = _reactNative.StyleSheet.create({
783
859
  marginBottom: 6
784
860
  },
785
861
  routeCard: {
786
- flexDirection: "row",
787
- alignItems: "center",
788
- justifyContent: "space-between",
789
- paddingVertical: 10,
790
- paddingHorizontal: 12,
791
862
  backgroundColor: _sharedUi.buoyColors.card,
792
863
  borderRadius: 6,
793
864
  borderWidth: 1,
794
865
  borderColor: _sharedUi.buoyColors.border,
866
+ overflow: "hidden",
795
867
  shadowColor: "#000",
796
868
  shadowOffset: {
797
869
  width: 0,
@@ -801,8 +873,16 @@ const styles = _reactNative.StyleSheet.create({
801
873
  shadowRadius: 2,
802
874
  elevation: 1
803
875
  },
876
+ routeHeader: {
877
+ flexDirection: "row",
878
+ alignItems: "center",
879
+ justifyContent: "space-between",
880
+ paddingVertical: 7,
881
+ paddingHorizontal: 10,
882
+ gap: 8
883
+ },
804
884
  expandIndicator: {
805
- width: 20,
885
+ width: 18,
806
886
  alignItems: "center"
807
887
  },
808
888
  routeHeaderLeft: {
@@ -851,76 +931,29 @@ const styles = _reactNative.StyleSheet.create({
851
931
  fontWeight: "600",
852
932
  fontFamily: "monospace"
853
933
  },
854
- routeDetails: {
855
- paddingHorizontal: 12,
856
- paddingTop: 8,
857
- paddingBottom: 12,
858
- gap: 12,
859
- borderTopWidth: 1,
860
- borderTopColor: _sharedUi.buoyColors.border,
861
- backgroundColor: _sharedUi.buoyColors.card,
862
- borderRadius: 6,
934
+ // Compact icon-sized Copy button in the header action cluster.
935
+ iconAction: {
936
+ width: 28,
937
+ height: 24,
938
+ borderRadius: 4,
939
+ backgroundColor: _sharedUi.buoyColors.input,
863
940
  borderWidth: 1,
864
941
  borderColor: _sharedUi.buoyColors.border,
865
- borderTopLeftRadius: 0,
866
- borderTopRightRadius: 0,
867
- marginTop: -6
868
- },
869
- paramsContainer: {
870
- gap: 6
871
- },
872
- paramsLabel: {
873
- fontSize: 10,
874
- color: _sharedUi.buoyColors.textSecondary,
875
- fontFamily: "monospace",
876
- marginBottom: 4
877
- },
878
- paramsRow: {
879
- flexDirection: "row",
880
- flexWrap: "wrap",
881
- gap: 6
942
+ alignItems: "center",
943
+ justifyContent: "center"
882
944
  },
883
- paramTag: {
884
- backgroundColor: "#F59E0B15",
885
- borderColor: "#F59E0B40",
886
- borderWidth: 1,
945
+ // Small "Go" pill in the header action cluster.
946
+ goButton: {
947
+ height: 24,
948
+ paddingHorizontal: 10,
887
949
  borderRadius: 4,
888
- paddingHorizontal: 6,
889
- paddingVertical: 2
890
- },
891
- paramText: {
892
- fontSize: 10,
893
- color: "#F59E0B",
894
- fontFamily: "monospace",
895
- fontWeight: "600"
896
- },
897
- routeButtons: {
898
- flexDirection: "row",
899
- gap: 6,
900
- marginBottom: 12
901
- },
902
- actionButton: {
903
- flexDirection: "row",
904
950
  alignItems: "center",
905
- gap: 4,
906
- backgroundColor: _sharedUi.buoyColors.input,
907
- borderRadius: 4,
908
- paddingHorizontal: 12,
909
- paddingVertical: 6,
910
- borderWidth: 1,
911
- borderColor: _sharedUi.buoyColors.border
912
- },
913
- actionButtonText: {
914
- fontSize: 12,
915
- color: _sharedUi.buoyColors.textSecondary,
916
- fontFamily: "monospace",
917
- fontWeight: "600"
918
- },
919
- navigateButton: {
951
+ justifyContent: "center",
920
952
  backgroundColor: _sharedUi.buoyColors.primary + "15",
953
+ borderWidth: 1,
921
954
  borderColor: _sharedUi.buoyColors.primary + "40"
922
955
  },
923
- navigateButtonText: {
956
+ goButtonText: {
924
957
  fontSize: 12,
925
958
  color: _sharedUi.buoyColors.primary,
926
959
  fontFamily: "monospace",
@@ -929,6 +962,9 @@ const styles = _reactNative.StyleSheet.create({
929
962
  childrenContainer: {
930
963
  borderLeftWidth: 2,
931
964
  borderLeftColor: _sharedUi.buoyColors.border,
932
- marginLeft: 16
965
+ marginLeft: 16,
966
+ marginRight: 8,
967
+ marginTop: 2,
968
+ marginBottom: 8
933
969
  }
934
970
  });
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.getExpoRouterStore = getExpoRouterStore;
7
7
  exports.getRouteNodeMetadata = getRouteNodeMetadata;
8
+ exports.isExpoRouterStoreSupported = isExpoRouterStoreSupported;
8
9
  exports.loadRouteNode = loadRouteNode;
9
10
  let cachedStore = null;
10
11
  let cachedStoreSource = null;
@@ -30,10 +31,26 @@ function logOnce(message, error) {
30
31
  * The storeRef gets populated when Expo Router's useStore() hook runs.
31
32
  * So we cache the store reference but its property values update over time.
32
33
  */
34
+ /**
35
+ * Whether the expo-router store can be loaded in this runtime. The store is
36
+ * pulled in via CommonJS `require`, which only exists under Metro/Node. On the
37
+ * web (e.g. the desktop dashboard rendering these RN tools via react-native-web)
38
+ * `require` is undefined, the route tree lives on the device — not here — and
39
+ * there is nothing to load. Callers use this to skip polling/logging.
40
+ */
41
+ function isExpoRouterStoreSupported() {
42
+ return typeof require !== "undefined";
43
+ }
33
44
  function getExpoRouterStore() {
34
45
  if (cachedStore) {
35
46
  return cachedStore;
36
47
  }
48
+
49
+ // No `require` (web): bail quietly rather than throwing ReferenceError and
50
+ // logging a misleading "install expo-router" message on the dashboard.
51
+ if (!isExpoRouterStoreSupported()) {
52
+ return null;
53
+ }
37
54
  const loadFromBuild = () => {
38
55
  try {
39
56
  const module = require("expo-router/build/global-state/router-store");
@@ -63,6 +63,12 @@ Object.defineProperty(exports, "routeEventStore", {
63
63
  return _routeEventStore.routeEventStore;
64
64
  }
65
65
  });
66
+ Object.defineProperty(exports, "routeEventsSyncAdapter", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _routeEventsSyncAdapter.routeEventsSyncAdapter;
70
+ }
71
+ });
66
72
  Object.defineProperty(exports, "routeEventsToolPreset", {
67
73
  enumerable: true,
68
74
  get: function () {
@@ -129,6 +135,7 @@ var _useRouteObserverReactNavigation = require("./useRouteObserverReactNavigatio
129
135
  var _useRouteEvents = require("./hooks/useRouteEvents");
130
136
  var _useRouteSitemap = require("./useRouteSitemap");
131
137
  var _useNavigationStack = require("./useNavigationStack");
138
+ var _routeEventsSyncAdapter = require("./sync/routeEventsSyncAdapter");
132
139
  var _RouteParser = require("./RouteParser");
133
140
  var _RouteObserver = require("./RouteObserver");
134
141
  var _routeEventStore = require("./stores/routeEventStore");
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.navigationStackStore = void 0;
7
+ /**
8
+ * Navigation Stack Store
9
+ *
10
+ * Holds the latest serializable navigation stack plus references to the live
11
+ * navigation action functions. The stack is captured inside the navigation tree
12
+ * by <RouteTracker /> (which runs useNavigationStack), and read by the
13
+ * route-events sync adapter so the dashboard can render the Stack tab and drive
14
+ * navigation on the device remotely.
15
+ *
16
+ * The stack itself (StackDisplayItem[]) is JSON-serializable and synced to the
17
+ * dashboard. The action functions are NOT serialized — they stay on the device
18
+ * and are invoked when the dashboard sends a remote action.
19
+ */
20
+
21
+ class NavigationStackStore {
22
+ stack = [];
23
+ actions = null;
24
+ listeners = new Set();
25
+ getStack() {
26
+ return this.stack;
27
+ }
28
+ setStack(stack) {
29
+ this.stack = stack;
30
+ this.listeners.forEach(listener => listener());
31
+ }
32
+ getActions() {
33
+ return this.actions;
34
+ }
35
+ setActions(actions) {
36
+ this.actions = actions;
37
+ }
38
+ subscribe(listener) {
39
+ this.listeners.add(listener);
40
+ return () => {
41
+ this.listeners.delete(listener);
42
+ };
43
+ }
44
+ }
45
+ const navigationStackStore = exports.navigationStackStore = new NavigationStackStore();