@buoy-gg/route-events 3.0.0 → 3.0.2

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/RouteTracker.js +38 -4
  2. package/lib/commonjs/components/NavigationStack.js +47 -31
  3. package/lib/commonjs/components/RoutesSitemap.js +176 -133
  4. package/lib/commonjs/expoRouterStore.js +17 -0
  5. package/lib/commonjs/stores/navigationStackStore.js +45 -0
  6. package/lib/commonjs/sync/routeEventsSyncAdapter.js +125 -3
  7. package/lib/commonjs/useRouteSitemap.js +28 -7
  8. package/lib/module/RouteTracker.js +39 -5
  9. package/lib/module/components/NavigationStack.js +47 -31
  10. package/lib/module/components/RoutesSitemap.js +177 -134
  11. package/lib/module/expoRouterStore.js +16 -0
  12. package/lib/module/stores/navigationStackStore.js +41 -0
  13. package/lib/module/sync/routeEventsSyncAdapter.js +125 -3
  14. package/lib/module/useRouteSitemap.js +29 -8
  15. package/lib/typescript/RouteTracker.d.ts.map +1 -1
  16. package/lib/typescript/components/NavigationStack.d.ts +18 -1
  17. package/lib/typescript/components/NavigationStack.d.ts.map +1 -1
  18. package/lib/typescript/components/RoutesSitemap.d.ts +19 -1
  19. package/lib/typescript/components/RoutesSitemap.d.ts.map +1 -1
  20. package/lib/typescript/expoRouterStore.d.ts +8 -0
  21. package/lib/typescript/expoRouterStore.d.ts.map +1 -1
  22. package/lib/typescript/index.d.ts +2 -1
  23. package/lib/typescript/index.d.ts.map +1 -1
  24. package/lib/typescript/stores/navigationStackStore.d.ts +33 -0
  25. package/lib/typescript/stores/navigationStackStore.d.ts.map +1 -0
  26. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts +52 -1
  27. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +1 -1
  28. package/lib/typescript/useRouteSitemap.d.ts +17 -0
  29. package/lib/typescript/useRouteSitemap.d.ts.map +1 -1
  30. package/package.json +5 -5
@@ -4,9 +4,12 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.RouteTracker = RouteTracker;
7
+ var _react = require("react");
7
8
  var _sharedUi = require("@buoy-gg/shared-ui");
8
9
  var _useRouteObserver = require("./useRouteObserver");
9
10
  var _useRouteObserverReactNavigation = require("./useRouteObserverReactNavigation");
11
+ var _useNavigationStack = require("./useNavigationStack");
12
+ var _navigationStackStore = require("./stores/navigationStackStore");
10
13
  var _jsxRuntime = require("react/jsx-runtime");
11
14
  /**
12
15
  * RouteTracker - A component to place inside your navigation tree
@@ -57,10 +60,41 @@ function ReactNavigationRouteTracker() {
57
60
  (0, _useRouteObserverReactNavigation.useRouteObserverReactNavigation)();
58
61
  return null;
59
62
  }
63
+
64
+ /**
65
+ * Mirrors the live navigation stack (and its action functions) into
66
+ * navigationStackStore so the route-events sync adapter can expose the Stack
67
+ * tab to the dashboard. Runs inside the navigation tree where the container ref
68
+ * context is available.
69
+ */
70
+ function NavigationStackCapture() {
71
+ const {
72
+ stack,
73
+ navigateToIndex,
74
+ popToIndex,
75
+ goBack,
76
+ popToTop
77
+ } = (0, _useNavigationStack.useNavigationStack)();
78
+ (0, _react.useEffect)(() => {
79
+ _navigationStackStore.navigationStackStore.setStack(stack);
80
+ }, [stack]);
81
+
82
+ // Keep the action references fresh (they change as the stack changes) so a
83
+ // remote action always operates on the current navigation state.
84
+ (0, _react.useEffect)(() => {
85
+ _navigationStackStore.navigationStackStore.setActions({
86
+ navigateToIndex,
87
+ popToIndex,
88
+ goBack,
89
+ popToTop
90
+ });
91
+ return () => _navigationStackStore.navigationStackStore.setActions(null);
92
+ });
93
+ return null;
94
+ }
60
95
  function RouteTracker() {
61
96
  const hasExpo = (0, _sharedUi.isExpoRouterAvailable)();
62
- if (hasExpo) {
63
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ExpoRouteTracker, {});
64
- }
65
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ReactNavigationRouteTracker, {});
97
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
98
+ children: [hasExpo ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ExpoRouteTracker, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(ReactNavigationRouteTracker, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(NavigationStackCapture, {})]
99
+ });
66
100
  }
@@ -22,24 +22,34 @@ var _jsxRuntime = require("react/jsx-runtime");
22
22
  // Types
23
23
  // ============================================================================
24
24
 
25
+ /** Stack manipulation a host can be asked to perform on the device. */
26
+
25
27
  // ============================================================================
26
28
  // Main Component
27
29
  // ============================================================================
28
30
 
29
31
  function NavigationStack({
30
- style
32
+ style,
33
+ injectedStack,
34
+ onAction
31
35
  }) {
32
- const {
33
- stack,
34
- focusedRoute,
35
- stackDepth,
36
- isAtRoot,
37
- isLoaded,
38
- error,
39
- navigateToIndex,
40
- goBack,
41
- popToTop
42
- } = (0, _useNavigationStack.useNavigationStack)();
36
+ const hookResult = (0, _useNavigationStack.useNavigationStack)();
37
+ const isInjected = injectedStack != null;
38
+
39
+ // Data source: injected (dashboard) or the local navigation container.
40
+ const stack = isInjected ? injectedStack : hookResult.stack;
41
+ const isLoaded = isInjected ? true : hookResult.isLoaded;
42
+ const error = isInjected ? null : hookResult.error;
43
+ const focusedRoute = (0, _react.useMemo)(() => stack.find(item => item.isFocused) ?? null, [stack]);
44
+ const stackDepth = stack.length;
45
+ const isAtRoot = stackDepth <= 1;
46
+
47
+ // Action wrappers: delegate to the host when provided, else act locally.
48
+ const navigateToIndex = index => onAction ? onAction("navigateToIndex", {
49
+ index
50
+ }) : hookResult.navigateToIndex(index);
51
+ const goBack = () => onAction ? onAction("goBack") : hookResult.goBack();
52
+ const popToTop = () => onAction ? onAction("popToTop") : hookResult.popToTop();
43
53
  const [expandedIndex, setExpandedIndex] = (0, _react.useState)(null);
44
54
  const [showHelp, setShowHelp] = (0, _react.useState)(false);
45
55
  const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
@@ -128,6 +138,26 @@ function NavigationStack({
128
138
  }
129
139
 
130
140
  // Handlers
141
+ // Confirm a destructive action. On device we use the native Alert; when an
142
+ // action delegate is set (dashboard, react-native-web) Alert button presses
143
+ // don't fire, so fall back to window.confirm.
144
+ const confirmDestructive = (title, message, confirmLabel, onConfirm) => {
145
+ if (onAction) {
146
+ const confirmFn = globalThis.window?.confirm;
147
+ if (!confirmFn || confirmFn(`${title}\n\n${message}`)) {
148
+ onConfirm();
149
+ }
150
+ return;
151
+ }
152
+ _reactNative.Alert.alert(title, message, [{
153
+ text: "Cancel",
154
+ style: "cancel"
155
+ }, {
156
+ text: confirmLabel,
157
+ style: "destructive",
158
+ onPress: onConfirm
159
+ }]);
160
+ };
131
161
  const handleGoBack = () => {
132
162
  // Gate behind Pro
133
163
  if (!isPro) {
@@ -150,14 +180,7 @@ function NavigationStack({
150
180
  _reactNative.Alert.alert("Already at Top", "Stack only has one screen");
151
181
  return;
152
182
  }
153
- _reactNative.Alert.alert("Pop to Top", "This will remove all screens except the root screen.", [{
154
- text: "Cancel",
155
- style: "cancel"
156
- }, {
157
- text: "Pop to Top",
158
- style: "destructive",
159
- onPress: popToTop
160
- }]);
183
+ confirmDestructive("Pop to Top", "This will remove all screens except the root screen.", "Pop to Top", popToTop);
161
184
  };
162
185
  const toggleExpand = index => {
163
186
  setExpandedIndex(expandedIndex === index ? null : index);
@@ -199,17 +222,10 @@ function NavigationStack({
199
222
  return;
200
223
  }
201
224
  const screensToRemove = stackDepth - 1 - selectedRoute.index;
202
- _reactNative.Alert.alert("Pop to Route", `Remove ${screensToRemove} screen${screensToRemove !== 1 ? "s" : ""} above ${selectedRoute.pathname}?`, [{
203
- text: "Cancel",
204
- style: "cancel"
205
- }, {
206
- text: "Pop",
207
- style: "destructive",
208
- onPress: () => {
209
- // Navigate to the selected route, which effectively pops everything above it
210
- navigateToIndex(selectedRoute.index);
211
- }
212
- }]);
225
+ const targetIndex = selectedRoute.index;
226
+ confirmDestructive("Pop to Route", `Remove ${screensToRemove} screen${screensToRemove !== 1 ? "s" : ""} above ${selectedRoute.pathname}?`, "Pop",
227
+ // Navigate to the selected route, which effectively pops everything above it
228
+ () => navigateToIndex(targetIndex));
213
229
  };
214
230
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
215
231
  style: [styles.container, style],
@@ -25,12 +25,30 @@ var _jsxRuntime = require("react/jsx-runtime");
25
25
  // Types
26
26
  // ============================================================================
27
27
 
28
+ // Synthetic route representing the app's home/index ("/"). Used by the
29
+ // "Home" toolbar shortcut so navigation works the same as tapping any route.
30
+ const HOME_ROUTE = {
31
+ path: "/",
32
+ name: "index",
33
+ type: "index",
34
+ params: [],
35
+ nodeType: "route",
36
+ contextKey: "/",
37
+ isInternal: false,
38
+ children: [],
39
+ depth: 0
40
+ };
41
+
28
42
  // ============================================================================
29
43
  // Main Component
30
44
  // ============================================================================
31
45
 
32
46
  function RoutesSitemap({
33
- style
47
+ style,
48
+ injectedRoutes,
49
+ injectedSource,
50
+ injectedLastUpdatedAt,
51
+ onNavigateRoute
34
52
  }) {
35
53
  const [searchQuery, setSearchQuery] = (0, _react.useState)("");
36
54
  const [isSearching, setIsSearching] = (0, _react.useState)(false);
@@ -52,6 +70,7 @@ function RoutesSitemap({
52
70
  groups,
53
71
  stats,
54
72
  isLoaded,
73
+ isSupported,
55
74
  filteredRoutes,
56
75
  routes,
57
76
  refresh,
@@ -59,7 +78,10 @@ function RoutesSitemap({
59
78
  source
60
79
  } = (0, _useRouteSitemap.useRouteSitemap)({
61
80
  searchQuery,
62
- sortBy: "path"
81
+ sortBy: "path",
82
+ injectedRoutes,
83
+ injectedSource,
84
+ injectedLastUpdatedAt
63
85
  });
64
86
 
65
87
  // Prepare copy data - memoized so it only rebuilds when dependencies change
@@ -147,6 +169,13 @@ function RoutesSitemap({
147
169
  }], "plain-text");
148
170
  }, [router]);
149
171
  const handleNavigate = (0, _react.useCallback)(route => {
172
+ // When a navigation delegate is supplied (dashboard → device), hand off
173
+ // entirely: the delegate owns Pro gating and param resolution.
174
+ if (onNavigateRoute) {
175
+ onNavigateRoute(route);
176
+ return;
177
+ }
178
+
150
179
  // Gate behind Pro
151
180
  if (!isPro) {
152
181
  setShowUpgradeModal(true);
@@ -182,7 +211,10 @@ function RoutesSitemap({
182
211
  } catch (error) {
183
212
  _reactNative.Alert.alert("Navigation Error", String(error));
184
213
  }
185
- }, [router, promptForParams, isPro]);
214
+ }, [router, promptForParams, isPro, onNavigateRoute]);
215
+ const handleGoHome = (0, _react.useCallback)(() => {
216
+ handleNavigate(HOME_ROUTE);
217
+ }, [handleNavigate]);
186
218
  const handleManualRefresh = (0, _react.useCallback)(() => {
187
219
  if (isRefreshing) return;
188
220
  setIsRefreshing(true);
@@ -217,7 +249,7 @@ function RoutesSitemap({
217
249
  style: styles.loadingContainer,
218
250
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
219
251
  style: styles.loadingText,
220
- children: "Loading routes..."
252
+ 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
253
  })
222
254
  })
223
255
  });
@@ -229,6 +261,20 @@ function RoutesSitemap({
229
261
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
230
262
  style: styles.actionsRow,
231
263
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
264
+ style: styles.actionWrapper,
265
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
266
+ style: styles.iconButton,
267
+ onPress: handleGoHome,
268
+ accessibilityLabel: "Go to home route",
269
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Home, {
270
+ size: 16,
271
+ color: _sharedUi.buoyColors.textSecondary
272
+ })
273
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
274
+ style: styles.actionLabel,
275
+ children: "Home"
276
+ })]
277
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
232
278
  style: styles.actionWrapper,
233
279
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
234
280
  value: copyAllData,
@@ -432,82 +478,92 @@ function RouteItemView({
432
478
  const hasParams = route.params.length > 0;
433
479
  const typeColor = getRouteTypeColor(route.type);
434
480
  const canNavigate = route.type !== "layout" && route.type !== "group";
435
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
481
+
482
+ // Leaf routes (no children) have nothing to drill into — expanding would only
483
+ // reveal the Copy/Go actions, so show those inline instead of behind a toggle.
484
+ const isExpandable = hasChildren;
485
+ const showDetails = isExpandable ? isExpanded : true;
486
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
436
487
  style: [styles.routeItem, {
437
488
  marginLeft: depth * 12
438
489
  }],
439
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
490
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
440
491
  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
492
  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,
493
+ style: styles.routeHeader,
494
+ children: [isExpandable ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
495
+ style: styles.routeHeaderLeft,
496
+ onPress: () => setIsExpanded(!isExpanded),
497
+ activeOpacity: 0.7,
498
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
499
+ style: styles.expandIndicator,
500
+ children: isExpanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
501
+ size: 14,
502
+ color: _sharedUi.buoyColors.textSecondary
503
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
504
+ size: 14,
505
+ color: _sharedUi.buoyColors.textSecondary
506
+ })
507
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
508
+ style: styles.routePath,
509
+ numberOfLines: 3,
510
+ children: route.path
511
+ })]
512
+ }) :
513
+ /*#__PURE__*/
514
+ // Non-toggling header for leaf routes (keeps the chevron column as a
515
+ // spacer so paths stay aligned with expandable siblings).
516
+ (0, _jsxRuntime.jsxs)(_reactNative.View, {
517
+ style: styles.routeHeaderLeft,
518
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
519
+ style: styles.expandIndicator
520
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
521
+ style: styles.routePath,
522
+ numberOfLines: 3,
523
+ children: route.path
524
+ })]
525
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
526
+ style: styles.routeHeaderActions,
527
+ children: [hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
528
+ style: styles.childCountBadge,
504
529
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
505
- style: styles.paramText,
506
- children: param
530
+ style: styles.childCountText,
531
+ children: route.children.length
507
532
  })
508
- }, param))
533
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
534
+ style: [styles.typeTag, {
535
+ backgroundColor: `${typeColor}15`,
536
+ borderColor: `${typeColor}40`
537
+ }],
538
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
539
+ style: [styles.typeText, {
540
+ color: typeColor
541
+ }],
542
+ children: route.type.toUpperCase()
543
+ })
544
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.InlineCopyButton, {
545
+ value: route.path,
546
+ buttonStyle: styles.iconAction
547
+ }), canNavigate && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
548
+ style: styles.goButton,
549
+ onPress: () => onNavigate(route),
550
+ activeOpacity: 0.7,
551
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
552
+ style: styles.goButtonText,
553
+ children: "Go"
554
+ })
555
+ })]
509
556
  })]
510
- }), hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
557
+ }), showDetails && hasParams && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
558
+ style: styles.paramsRow,
559
+ children: route.params.map(param => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
560
+ style: styles.paramTag,
561
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
562
+ style: styles.paramText,
563
+ children: param
564
+ })
565
+ }, param))
566
+ }), showDetails && hasChildren && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
511
567
  style: styles.childrenContainer,
512
568
  children: route.children.map((child, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteItemView, {
513
569
  route: child,
@@ -515,7 +571,7 @@ function RouteItemView({
515
571
  onNavigate: onNavigate
516
572
  }, `${child.path}-${index}`))
517
573
  })]
518
- })]
574
+ })
519
575
  });
520
576
  }
521
577
 
@@ -569,7 +625,11 @@ const styles = _reactNative.StyleSheet.create({
569
625
  loadingText: {
570
626
  color: _sharedUi.buoyColors.textSecondary,
571
627
  fontSize: 14,
572
- fontFamily: "monospace"
628
+ fontFamily: "monospace",
629
+ textAlign: "center",
630
+ lineHeight: 20,
631
+ paddingHorizontal: 32,
632
+ maxWidth: 420
573
633
  },
574
634
  header: {
575
635
  flexDirection: "column",
@@ -581,7 +641,7 @@ const styles = _reactNative.StyleSheet.create({
581
641
  actionsRow: {
582
642
  flexDirection: "row",
583
643
  alignItems: "center",
584
- justifyContent: "flex-end",
644
+ justifyContent: "space-between",
585
645
  gap: 12,
586
646
  paddingBottom: 4
587
647
  },
@@ -616,6 +676,7 @@ const styles = _reactNative.StyleSheet.create({
616
676
  letterSpacing: 0.5
617
677
  },
618
678
  actionWrapper: {
679
+ flex: 1,
619
680
  alignItems: "center",
620
681
  justifyContent: "center",
621
682
  gap: 4,
@@ -783,15 +844,11 @@ const styles = _reactNative.StyleSheet.create({
783
844
  marginBottom: 6
784
845
  },
785
846
  routeCard: {
786
- flexDirection: "row",
787
- alignItems: "center",
788
- justifyContent: "space-between",
789
- paddingVertical: 10,
790
- paddingHorizontal: 12,
791
847
  backgroundColor: _sharedUi.buoyColors.card,
792
848
  borderRadius: 6,
793
849
  borderWidth: 1,
794
850
  borderColor: _sharedUi.buoyColors.border,
851
+ overflow: "hidden",
795
852
  shadowColor: "#000",
796
853
  shadowOffset: {
797
854
  width: 0,
@@ -801,8 +858,16 @@ const styles = _reactNative.StyleSheet.create({
801
858
  shadowRadius: 2,
802
859
  elevation: 1
803
860
  },
861
+ routeHeader: {
862
+ flexDirection: "row",
863
+ alignItems: "center",
864
+ justifyContent: "space-between",
865
+ paddingVertical: 7,
866
+ paddingHorizontal: 10,
867
+ gap: 8
868
+ },
804
869
  expandIndicator: {
805
- width: 20,
870
+ width: 18,
806
871
  alignItems: "center"
807
872
  },
808
873
  routeHeaderLeft: {
@@ -851,34 +916,41 @@ const styles = _reactNative.StyleSheet.create({
851
916
  fontWeight: "600",
852
917
  fontFamily: "monospace"
853
918
  },
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,
919
+ // Compact icon-sized Copy button in the header action cluster.
920
+ iconAction: {
921
+ width: 28,
922
+ height: 24,
923
+ borderRadius: 4,
924
+ backgroundColor: _sharedUi.buoyColors.input,
863
925
  borderWidth: 1,
864
926
  borderColor: _sharedUi.buoyColors.border,
865
- borderTopLeftRadius: 0,
866
- borderTopRightRadius: 0,
867
- marginTop: -6
927
+ alignItems: "center",
928
+ justifyContent: "center"
868
929
  },
869
- paramsContainer: {
870
- gap: 6
930
+ // Small "Go" pill in the header action cluster.
931
+ goButton: {
932
+ height: 24,
933
+ paddingHorizontal: 10,
934
+ borderRadius: 4,
935
+ alignItems: "center",
936
+ justifyContent: "center",
937
+ backgroundColor: _sharedUi.buoyColors.primary + "15",
938
+ borderWidth: 1,
939
+ borderColor: _sharedUi.buoyColors.primary + "40"
871
940
  },
872
- paramsLabel: {
873
- fontSize: 10,
874
- color: _sharedUi.buoyColors.textSecondary,
941
+ goButtonText: {
942
+ fontSize: 12,
943
+ color: _sharedUi.buoyColors.primary,
875
944
  fontFamily: "monospace",
876
- marginBottom: 4
945
+ fontWeight: "600"
877
946
  },
878
947
  paramsRow: {
879
948
  flexDirection: "row",
880
949
  flexWrap: "wrap",
881
- gap: 6
950
+ gap: 6,
951
+ paddingHorizontal: 10,
952
+ paddingTop: 2,
953
+ paddingBottom: 8
882
954
  },
883
955
  paramTag: {
884
956
  backgroundColor: "#F59E0B15",
@@ -894,41 +966,12 @@ const styles = _reactNative.StyleSheet.create({
894
966
  fontFamily: "monospace",
895
967
  fontWeight: "600"
896
968
  },
897
- routeButtons: {
898
- flexDirection: "row",
899
- gap: 6,
900
- marginBottom: 12
901
- },
902
- actionButton: {
903
- flexDirection: "row",
904
- 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: {
920
- backgroundColor: _sharedUi.buoyColors.primary + "15",
921
- borderColor: _sharedUi.buoyColors.primary + "40"
922
- },
923
- navigateButtonText: {
924
- fontSize: 12,
925
- color: _sharedUi.buoyColors.primary,
926
- fontFamily: "monospace",
927
- fontWeight: "600"
928
- },
929
969
  childrenContainer: {
930
970
  borderLeftWidth: 2,
931
971
  borderLeftColor: _sharedUi.buoyColors.border,
932
- marginLeft: 16
972
+ marginLeft: 16,
973
+ marginRight: 8,
974
+ marginTop: 2,
975
+ marginBottom: 8
933
976
  }
934
977
  });
@@ -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");