@buoy-gg/route-events 7.0.41 → 7.0.43

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 (102) hide show
  1. package/README.md +11 -64
  2. package/lib/commonjs/RouteObserver.js +1 -54
  3. package/lib/commonjs/RouteParser.js +1 -314
  4. package/lib/commonjs/RouteTracker.js +1 -112
  5. package/lib/commonjs/RouteTracker.web.js +1 -12
  6. package/lib/commonjs/browserRouteRegistry.js +1 -30
  7. package/lib/commonjs/browserRoutes.js +1 -96
  8. package/lib/commonjs/components/NavigationStack.js +2 -572
  9. package/lib/commonjs/components/ReactNavigationRoutes.js +1 -836
  10. package/lib/commonjs/components/ReactNavigationRoutes.web.js +1 -12
  11. package/lib/commonjs/components/RouteEventDetail.js +1 -97
  12. package/lib/commonjs/components/RouteEventExpandedContent.js +1 -204
  13. package/lib/commonjs/components/RouteEventItemCompact.js +1 -191
  14. package/lib/commonjs/components/RouteEventsModalWithTabs.js +1 -734
  15. package/lib/commonjs/components/RouteEventsTimeline.js +1 -107
  16. package/lib/commonjs/components/RouteFilterViewV2.js +1 -42
  17. package/lib/commonjs/components/RoutesSitemap.js +2 -1035
  18. package/lib/commonjs/expoRouterStore.js +1 -121
  19. package/lib/commonjs/expoRouterStore.web.js +1 -44
  20. package/lib/commonjs/hooks/useRouteEvents.js +1 -57
  21. package/lib/commonjs/index.js +1 -168
  22. package/lib/commonjs/preset.js +1 -69
  23. package/lib/commonjs/stores/navigationStackStore.js +1 -45
  24. package/lib/commonjs/stores/routeEventStore.js +1 -136
  25. package/lib/commonjs/sync/routeEventsSyncAdapter.js +1 -275
  26. package/lib/commonjs/useNavigationStack.js +1 -408
  27. package/lib/commonjs/useNavigationStack.web.js +1 -32
  28. package/lib/commonjs/useRouteObserver.js +1 -73
  29. package/lib/commonjs/useRouteObserverReactNavigation.js +1 -109
  30. package/lib/commonjs/useRouteSitemap.js +1 -255
  31. package/lib/commonjs/utils/routeTemplate.js +1 -79
  32. package/lib/commonjs/utils/safeReactNavigation.js +1 -194
  33. package/lib/commonjs/utils/safeReactNavigation.web.js +1 -36
  34. package/lib/commonjs/utils/testIDs.js +1 -63
  35. package/lib/module/RouteObserver.js +1 -49
  36. package/lib/module/RouteParser.js +1 -309
  37. package/lib/module/RouteTracker.js +1 -109
  38. package/lib/module/RouteTracker.web.js +1 -8
  39. package/lib/module/browserRouteRegistry.js +1 -24
  40. package/lib/module/browserRoutes.js +1 -92
  41. package/lib/module/components/NavigationStack.js +2 -568
  42. package/lib/module/components/ReactNavigationRoutes.js +1 -832
  43. package/lib/module/components/ReactNavigationRoutes.web.js +1 -3
  44. package/lib/module/components/RouteEventDetail.js +1 -93
  45. package/lib/module/components/RouteEventExpandedContent.js +1 -200
  46. package/lib/module/components/RouteEventItemCompact.js +1 -187
  47. package/lib/module/components/RouteEventsModalWithTabs.js +1 -731
  48. package/lib/module/components/RouteEventsTimeline.js +1 -103
  49. package/lib/module/components/RouteFilterViewV2.js +1 -38
  50. package/lib/module/components/RoutesSitemap.js +2 -1033
  51. package/lib/module/expoRouterStore.js +1 -114
  52. package/lib/module/expoRouterStore.web.js +1 -37
  53. package/lib/module/hooks/useRouteEvents.js +1 -54
  54. package/lib/module/index.js +1 -70
  55. package/lib/module/preset.js +1 -66
  56. package/lib/module/stores/navigationStackStore.js +1 -41
  57. package/lib/module/stores/routeEventStore.js +1 -128
  58. package/lib/module/sync/routeEventsSyncAdapter.js +1 -273
  59. package/lib/module/useNavigationStack.js +1 -404
  60. package/lib/module/useNavigationStack.web.js +1 -28
  61. package/lib/module/useRouteObserver.js +1 -70
  62. package/lib/module/useRouteObserverReactNavigation.js +1 -106
  63. package/lib/module/useRouteSitemap.js +1 -250
  64. package/lib/module/utils/routeTemplate.js +1 -73
  65. package/lib/module/utils/safeReactNavigation.js +1 -185
  66. package/lib/module/utils/safeReactNavigation.web.js +1 -27
  67. package/lib/module/utils/testIDs.js +1 -55
  68. package/lib/web/index.mjs +4 -5
  69. package/package.json +6 -11
  70. package/lib/typescript/RouteObserver.d.ts.map +0 -1
  71. package/lib/typescript/RouteParser.d.ts.map +0 -1
  72. package/lib/typescript/RouteTracker.d.ts.map +0 -1
  73. package/lib/typescript/RouteTracker.web.d.ts.map +0 -1
  74. package/lib/typescript/browserRouteRegistry.d.ts.map +0 -1
  75. package/lib/typescript/browserRoutes.d.ts.map +0 -1
  76. package/lib/typescript/components/NavigationStack.d.ts.map +0 -1
  77. package/lib/typescript/components/ReactNavigationRoutes.d.ts.map +0 -1
  78. package/lib/typescript/components/ReactNavigationRoutes.web.d.ts.map +0 -1
  79. package/lib/typescript/components/RouteEventDetail.d.ts.map +0 -1
  80. package/lib/typescript/components/RouteEventExpandedContent.d.ts.map +0 -1
  81. package/lib/typescript/components/RouteEventItemCompact.d.ts.map +0 -1
  82. package/lib/typescript/components/RouteEventsModalWithTabs.d.ts.map +0 -1
  83. package/lib/typescript/components/RouteEventsTimeline.d.ts.map +0 -1
  84. package/lib/typescript/components/RouteFilterViewV2.d.ts.map +0 -1
  85. package/lib/typescript/components/RoutesSitemap.d.ts.map +0 -1
  86. package/lib/typescript/expoRouterStore.d.ts.map +0 -1
  87. package/lib/typescript/expoRouterStore.web.d.ts.map +0 -1
  88. package/lib/typescript/hooks/useRouteEvents.d.ts.map +0 -1
  89. package/lib/typescript/index.d.ts.map +0 -1
  90. package/lib/typescript/preset.d.ts.map +0 -1
  91. package/lib/typescript/stores/navigationStackStore.d.ts.map +0 -1
  92. package/lib/typescript/stores/routeEventStore.d.ts.map +0 -1
  93. package/lib/typescript/sync/routeEventsSyncAdapter.d.ts.map +0 -1
  94. package/lib/typescript/useNavigationStack.d.ts.map +0 -1
  95. package/lib/typescript/useNavigationStack.web.d.ts.map +0 -1
  96. package/lib/typescript/useRouteObserver.d.ts.map +0 -1
  97. package/lib/typescript/useRouteObserverReactNavigation.d.ts.map +0 -1
  98. package/lib/typescript/useRouteSitemap.d.ts.map +0 -1
  99. package/lib/typescript/utils/routeTemplate.d.ts.map +0 -1
  100. package/lib/typescript/utils/safeReactNavigation.d.ts.map +0 -1
  101. package/lib/typescript/utils/safeReactNavigation.web.d.ts.map +0 -1
  102. package/lib/typescript/utils/testIDs.d.ts.map +0 -1
@@ -1,1034 +1,3 @@
1
- "use strict";
1
+ "use strict";import{useState as B,useCallback as w,useMemo as H,useEffect as de}from"react";import{night as o,ToolEmptyState as pe,monoFont as c}from"@buoy-gg/shared-ui";import{View as n,Text as l,TextInput as ue,TouchableOpacity as C,ScrollView as he,StyleSheet as ye,Alert as T,Platform as ge}from"react-native";import{useSafeRouter as fe}from"@buoy-gg/shared-ui";import{Search as q,ChevronDown as J,ChevronRight as X,Home as me,InlineCopyButton as be,CopyButton as xe,RefreshCw as Ce,formatRelativeTime as Se,ProUpgradeModal as De}from"@buoy-gg/shared-ui";import{useIsPro as Ie}from"@buoy-gg/license";import{useRouteSitemap as Be}from"../useRouteSitemap";import{sitemapGroupTestID as Y,sitemapRouteTestID as Te}from"../utils/testIDs";import{jsx as t,jsxs as i}from"react/jsx-runtime";const d=ge.OS==="web";const F=d?22:16;const Re={path:"/",name:"index",type:"index",params:[],nodeType:"route",contextKey:"/",isInternal:false,children:[],depth:0};export function RoutesSitemap({style:r,injectedRoutes:g,injectedSource:f,injectedLastUpdatedAt:S,onNavigateRoute:b}){const[p,D]=B("");const[z,h]=B(false);const[W,E]=B(new Set(["Static Routes","Dynamic Routes"]));const[x,v]=B(false);const[P,V]=B(false);const $=Ie();de(()=>{if(P&&$){V(false)}},[P,$]);const L=fe();const{groups:k,stats:u,isLoaded:te,isSupported:oe,filteredRoutes:j,routes:_,refresh:K,lastUpdatedAt:O,source:G}=Be({searchQuery:p,sortBy:"path",injectedRoutes:g,injectedSource:f,injectedLastUpdatedAt:S});const re=H(()=>{return{summary:{total:u.total,static:u.static,dynamic:u.dynamic,layouts:u.layouts,groups:u.groups,timestamp:new Date().toISOString()},groups:k.map(a=>({title:a.title,description:a.description,count:a.routes.length,routes:a.routes.map(s=>({path:s.path,name:s.name,type:s.type,params:s.params,depth:s.depth,hasChildren:s.children.length>0,childrenCount:s.children.length}))})),allRoutes:_.map(a=>a.path)}},[k,u,_]);const ae=w(a=>{E(s=>{const y=new Set(s);if(y.has(a)){y.delete(a)}else{y.add(a)}return y})},[]);const N=w((a,s=0,y={})=>{const U=a.params;if(s>=U.length){let m=a.path;Object.entries(y).forEach(([I,Z])=>{if(a.type==="catch-all"){m=m.replace(`[...${I}]`,Z)}else{m=m.replace(`[${I}]`,Z)}});try{L.push(m)}catch(I){T.alert("Navigation Error",String(I))}return}const M=U[s];const ce=a.type==="catch-all"?`[...${M}]`:`[${M}]`;T.prompt("Enter Parameter Value",`Enter value for ${ce}:
2
2
 
3
- /**
4
- * RoutesSitemap - Visual sitemap of all app routes
5
- *
6
- * Displays parsed route information from Expo Router with:
7
- * - Search/filter
8
- * - Organized groups
9
- * - Route details
10
- * - Copy to clipboard
11
- * - Navigation
12
- */
13
-
14
- import { useState, useCallback, useMemo, useEffect } from "react";
15
- import { night, ToolEmptyState, monoFont } from "@buoy-gg/shared-ui";
16
- import { View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet, Alert, Platform } from "react-native";
17
- import { useSafeRouter } from "@buoy-gg/shared-ui";
18
- import { Search, ChevronDown, ChevronRight, Home, InlineCopyButton, CopyButton, RefreshCw, formatRelativeTime, ProUpgradeModal } from "@buoy-gg/shared-ui";
19
- import { useIsPro } from "@buoy-gg/license";
20
- import { useRouteSitemap } from "../useRouteSitemap";
21
- import { sitemapGroupTestID, sitemapRouteTestID } from "../utils/testIDs";
22
-
23
- // ============================================================================
24
- // Types
25
- // ============================================================================
26
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
27
- // On desktop (web) the toolbar has far more room, so scale the action buttons
28
- // up for easier clicking and a less cramped header.
29
- const IS_DESKTOP = Platform.OS === "web";
30
- const ACTION_ICON_SIZE = IS_DESKTOP ? 22 : 16;
31
-
32
- // Synthetic route representing the app's home/index ("/"). Used by the
33
- // "Home" toolbar shortcut so navigation works the same as tapping any route.
34
- const HOME_ROUTE = {
35
- path: "/",
36
- name: "index",
37
- type: "index",
38
- params: [],
39
- nodeType: "route",
40
- contextKey: "/",
41
- isInternal: false,
42
- children: [],
43
- depth: 0
44
- };
45
-
46
- // ============================================================================
47
- // Main Component
48
- // ============================================================================
49
-
50
- export function RoutesSitemap({
51
- style,
52
- injectedRoutes,
53
- injectedSource,
54
- injectedLastUpdatedAt,
55
- onNavigateRoute
56
- }) {
57
- const [searchQuery, setSearchQuery] = useState("");
58
- const [isSearching, setIsSearching] = useState(false);
59
- const [expandedGroups, setExpandedGroups] = useState(new Set(["Static Routes", "Dynamic Routes"]));
60
- const [isRefreshing, setIsRefreshing] = useState(false);
61
- const [showUpgradeModal, setShowUpgradeModal] = useState(false);
62
-
63
- // Check Pro status internally
64
- const isPro = useIsPro();
65
-
66
- // Auto-close upgrade modal when user becomes Pro
67
- useEffect(() => {
68
- if (showUpgradeModal && isPro) {
69
- setShowUpgradeModal(false);
70
- }
71
- }, [showUpgradeModal, isPro]);
72
- const router = useSafeRouter();
73
- const {
74
- groups,
75
- stats,
76
- isLoaded,
77
- isSupported,
78
- filteredRoutes,
79
- routes,
80
- refresh,
81
- lastUpdatedAt,
82
- source
83
- } = useRouteSitemap({
84
- searchQuery,
85
- sortBy: "path",
86
- injectedRoutes,
87
- injectedSource,
88
- injectedLastUpdatedAt
89
- });
90
-
91
- // Prepare copy data - memoized so it only rebuilds when dependencies change
92
- const copyAllData = useMemo(() => {
93
- return {
94
- summary: {
95
- total: stats.total,
96
- static: stats.static,
97
- dynamic: stats.dynamic,
98
- layouts: stats.layouts,
99
- groups: stats.groups,
100
- timestamp: new Date().toISOString()
101
- },
102
- groups: groups.map(group => ({
103
- title: group.title,
104
- description: group.description,
105
- count: group.routes.length,
106
- routes: group.routes.map(route => ({
107
- path: route.path,
108
- name: route.name,
109
- type: route.type,
110
- params: route.params,
111
- depth: route.depth,
112
- hasChildren: route.children.length > 0,
113
- childrenCount: route.children.length
114
- }))
115
- })),
116
- allRoutes: routes.map(route => route.path)
117
- };
118
- }, [groups, stats, routes]);
119
- const handleToggleGroup = useCallback(groupTitle => {
120
- setExpandedGroups(prev => {
121
- const next = new Set(prev);
122
- if (next.has(groupTitle)) {
123
- next.delete(groupTitle);
124
- } else {
125
- next.add(groupTitle);
126
- }
127
- return next;
128
- });
129
- }, []);
130
- const promptForParams = useCallback((route, paramIndex = 0, collectedParams = {}) => {
131
- const params = route.params;
132
- if (paramIndex >= params.length) {
133
- // All parameters collected, build path and navigate
134
- let finalPath = route.path;
135
-
136
- // Replace each parameter in the path
137
- Object.entries(collectedParams).forEach(([param, value]) => {
138
- if (route.type === "catch-all") {
139
- // For catch-all routes, replace [...param] with the value
140
- finalPath = finalPath.replace(`[...${param}]`, value);
141
- } else {
142
- // For regular dynamic routes, replace [param] with the value
143
- finalPath = finalPath.replace(`[${param}]`, value);
144
- }
145
- });
146
-
147
- // Navigate
148
- try {
149
- router.push(finalPath);
150
- } catch (error) {
151
- Alert.alert("Navigation Error", String(error));
152
- }
153
- return;
154
- }
155
- const currentParam = params[paramIndex];
156
- const paramDisplay = route.type === "catch-all" ? `[...${currentParam}]` : `[${currentParam}]`;
157
- Alert.prompt("Enter Parameter Value", `Enter value for ${paramDisplay}:\n\nRoute: ${route.path}`, [{
158
- text: "Cancel",
159
- style: "cancel"
160
- }, {
161
- text: paramIndex < params.length - 1 ? "Next" : "Navigate",
162
- onPress: value => {
163
- if (value && value.trim()) {
164
- const newParams = {
165
- ...collectedParams,
166
- [currentParam]: value.trim()
167
- };
168
- promptForParams(route, paramIndex + 1, newParams);
169
- } else {
170
- Alert.alert("Invalid Value", "Please enter a value for the parameter");
171
- }
172
- }
173
- }], "plain-text");
174
- }, [router]);
175
- const handleNavigate = useCallback((route, options) => {
176
- // When a navigation delegate is supplied (dashboard → device), hand off
177
- // entirely: the delegate owns Pro gating and param resolution.
178
- if (onNavigateRoute) {
179
- onNavigateRoute(route);
180
- return;
181
- }
182
-
183
- // Gate behind Pro (the Home shortcut bypasses this — it's always free)
184
- if (!isPro && !options?.bypassPro) {
185
- setShowUpgradeModal(true);
186
- return;
187
- }
188
-
189
- // Don't navigate to layouts or groups
190
- if (route.type === "layout" || route.type === "group") {
191
- Alert.alert("Cannot Navigate", `${route.type === "layout" ? "Layouts" : "Route groups"} are not navigable routes`);
192
- return;
193
- }
194
-
195
- // For dynamic routes, prompt for parameters
196
- if (route.type === "dynamic" || route.type === "catch-all") {
197
- if (route.params.length === 0) {
198
- // No parameters despite being dynamic? Just navigate
199
- try {
200
- router.push(route.path);
201
- } catch (error) {
202
- Alert.alert("Navigation Error", String(error));
203
- }
204
- return;
205
- }
206
-
207
- // Start the parameter prompting flow
208
- promptForParams(route);
209
- return;
210
- }
211
-
212
- // Navigate to static route
213
- try {
214
- router.push(route.path);
215
- } catch (error) {
216
- Alert.alert("Navigation Error", String(error));
217
- }
218
- }, [router, promptForParams, isPro, onNavigateRoute]);
219
- const handleGoHome = useCallback(() => {
220
- handleNavigate(HOME_ROUTE, {
221
- bypassPro: true
222
- });
223
- }, [handleNavigate]);
224
- const handleManualRefresh = useCallback(() => {
225
- if (isRefreshing) return;
226
- setIsRefreshing(true);
227
- refresh();
228
- setTimeout(() => setIsRefreshing(false), 400);
229
- }, [isRefreshing, refresh]);
230
- const lastRefreshLabel = useMemo(() => {
231
- if (!lastUpdatedAt) return "Awaiting route data";
232
- return `Updated ${formatRelativeTime(lastUpdatedAt)}`;
233
- }, [lastUpdatedAt]);
234
- const sourceLabel = useMemo(() => {
235
- if (!source) return "Source: unknown";
236
- return `Source: ${source}`;
237
- }, [source]);
238
-
239
- // When searching, show filtered routes, otherwise show groups
240
- const displayGroups = useMemo(() => {
241
- if (searchQuery && filteredRoutes.length > 0) {
242
- // Create a single "Search Results" group
243
- return [{
244
- title: "Search Results",
245
- icon: "",
246
- routes: filteredRoutes
247
- }];
248
- }
249
- return groups;
250
- }, [searchQuery, filteredRoutes, groups]);
251
- if (!isLoaded) {
252
- return /*#__PURE__*/_jsx(View, {
253
- style: [styles.container, style],
254
- children: /*#__PURE__*/_jsx(View, {
255
- style: styles.loadingContainer,
256
- children: /*#__PURE__*/_jsx(Text, {
257
- style: styles.loadingText,
258
- testID: "buoy-routes-sitemap-loading",
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."
260
- })
261
- })
262
- });
263
- }
264
- return /*#__PURE__*/_jsxs(View, {
265
- style: [styles.container, style],
266
- children: [isSearching &&
267
- /*#__PURE__*/
268
- // The wrapper carries the handle as well as the field: a TextInput's
269
- // own testID never reaches the XCUITest element tree on this RN
270
- // version, so the a11y lane has nothing else to aim at.
271
- _jsxs(View, {
272
- style: styles.searchContainer,
273
- testID: "buoy-routes-sitemap-search-field",
274
- children: [/*#__PURE__*/_jsx(Search, {
275
- size: 16,
276
- color: night.color.textTertiary
277
- }), /*#__PURE__*/_jsx(TextInput, {
278
- style: styles.searchInput,
279
- placeholder: "Search routes...",
280
- placeholderTextColor: night.color.placeholder,
281
- value: searchQuery,
282
- onChangeText: setSearchQuery,
283
- autoCapitalize: "none",
284
- autoCorrect: false,
285
- autoFocus: true,
286
- testID: "buoy-routes-sitemap-search",
287
- accessibilityLabel: "Search routes"
288
- }), searchQuery.length > 0 && /*#__PURE__*/_jsx(TouchableOpacity, {
289
- onPress: () => setSearchQuery(""),
290
- style: styles.clearButton,
291
- testID: "buoy-routes-sitemap-search-clear",
292
- accessibilityRole: "button",
293
- accessibilityLabel: "Clear route search",
294
- children: /*#__PURE__*/_jsx(Text, {
295
- style: styles.clearButtonText,
296
- children: "\u2715"
297
- })
298
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
299
- style: styles.closeSearchButton,
300
- onPress: () => {
301
- setIsSearching(false);
302
- setSearchQuery("");
303
- },
304
- testID: "buoy-routes-sitemap-search-done",
305
- accessibilityRole: "button",
306
- accessibilityLabel: "Done searching routes",
307
- children: /*#__PURE__*/_jsx(Text, {
308
- style: styles.closeSearchText,
309
- children: "Done"
310
- })
311
- })]
312
- }), /*#__PURE__*/_jsxs(ScrollView, {
313
- testID: "buoy-routes-sitemap-scroll",
314
- style: styles.scrollView,
315
- contentContainerStyle: styles.scrollContent,
316
- children: [!isSearching && /*#__PURE__*/_jsxs(View, {
317
- style: styles.header,
318
- children: [/*#__PURE__*/_jsxs(View, {
319
- style: styles.actionsRow,
320
- children: [/*#__PURE__*/_jsxs(View, {
321
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
322
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
323
- style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
324
- onPress: handleGoHome,
325
- accessibilityLabel: "Go to home route",
326
- accessibilityRole: "button",
327
- testID: "buoy-routes-sitemap-home",
328
- children: /*#__PURE__*/_jsx(Home, {
329
- size: ACTION_ICON_SIZE,
330
- color: night.color.textSecondary
331
- })
332
- }), /*#__PURE__*/_jsx(Text, {
333
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
334
- children: "Home"
335
- })]
336
- }), /*#__PURE__*/_jsxs(View, {
337
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
338
- children: [/*#__PURE__*/_jsx(CopyButton, {
339
- value: copyAllData,
340
- testID: "buoy-routes-sitemap-copy",
341
- size: IS_DESKTOP ? ACTION_ICON_SIZE : 14,
342
- buttonStyle: IS_DESKTOP ? styles.actionButtonHeaderDesktop : styles.actionButtonHeader
343
- }), /*#__PURE__*/_jsx(Text, {
344
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
345
- children: "Copy"
346
- })]
347
- }), /*#__PURE__*/_jsxs(View, {
348
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
349
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
350
- style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop, isRefreshing && styles.refreshButtonDisabled],
351
- onPress: handleManualRefresh,
352
- disabled: isRefreshing,
353
- testID: "buoy-routes-sitemap-refresh",
354
- accessibilityRole: "button",
355
- accessibilityLabel: "Refresh route tree",
356
- children: /*#__PURE__*/_jsx(RefreshCw, {
357
- size: ACTION_ICON_SIZE,
358
- color: isRefreshing ? night.color.textTertiary : night.color.textSecondary
359
- })
360
- }), /*#__PURE__*/_jsx(Text, {
361
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
362
- children: "Refresh"
363
- })]
364
- }), /*#__PURE__*/_jsxs(View, {
365
- style: [styles.actionWrapper, IS_DESKTOP && styles.actionWrapperDesktop],
366
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
367
- style: [styles.iconButton, IS_DESKTOP && styles.iconButtonDesktop],
368
- onPress: () => setIsSearching(true),
369
- testID: "buoy-routes-sitemap-search-open",
370
- accessibilityRole: "button",
371
- accessibilityLabel: "Search routes",
372
- children: /*#__PURE__*/_jsx(Search, {
373
- size: ACTION_ICON_SIZE,
374
- color: night.color.textSecondary
375
- })
376
- }), /*#__PURE__*/_jsx(Text, {
377
- style: [styles.actionLabel, IS_DESKTOP && styles.actionLabelDesktop],
378
- children: "Search"
379
- })]
380
- })]
381
- }), /*#__PURE__*/_jsxs(View, {
382
- style: styles.statsGrid,
383
- children: [/*#__PURE__*/_jsx(StatItem, {
384
- value: stats.total,
385
- label: "Total",
386
- id: "total"
387
- }), /*#__PURE__*/_jsx(StatItem, {
388
- value: stats.static,
389
- label: "Static",
390
- id: "static"
391
- }), /*#__PURE__*/_jsx(StatItem, {
392
- value: stats.dynamic,
393
- label: "Dynamic",
394
- id: "dynamic"
395
- }), /*#__PURE__*/_jsx(StatItem, {
396
- value: stats.catchAll,
397
- label: "Catch-All",
398
- id: "catch-all"
399
- }), /*#__PURE__*/_jsx(StatItem, {
400
- value: stats.layouts,
401
- label: "Layouts",
402
- id: "layouts"
403
- }), /*#__PURE__*/_jsx(StatItem, {
404
- value: stats.groups,
405
- label: "Groups",
406
- id: "groups"
407
- })]
408
- })]
409
- }), displayGroups.length === 0 ? /*#__PURE__*/_jsx(ToolEmptyState, {
410
- testID: "buoy-routes-sitemap-empty"
411
- // map = "route inspector; where am I in the navigator" — the one
412
- // OTTO_USES sentence written for this screen.
413
- ,
414
- otto: "map",
415
- title: "No routes found",
416
- message: "Nothing in the router matches. Clear the search to see the whole tree."
417
- }) : displayGroups.map(group => /*#__PURE__*/_jsx(RouteGroupView, {
418
- group: group,
419
- isExpanded: searchQuery.length > 0 || expandedGroups.has(group.title),
420
- onToggleExpand: () => handleToggleGroup(group.title),
421
- onNavigate: handleNavigate
422
- }, group.title)), /*#__PURE__*/_jsxs(View, {
423
- style: styles.metaFooter,
424
- children: [/*#__PURE__*/_jsx(Text, {
425
- style: styles.metaText,
426
- testID: "buoy-routes-sitemap-updated",
427
- children: lastRefreshLabel
428
- }), /*#__PURE__*/_jsx(View, {
429
- style: styles.sourceBadge,
430
- children: /*#__PURE__*/_jsx(Text, {
431
- style: styles.sourceText,
432
- testID: "buoy-routes-sitemap-source",
433
- children: sourceLabel
434
- })
435
- })]
436
- })]
437
- }), /*#__PURE__*/_jsx(ProUpgradeModal, {
438
- visible: showUpgradeModal,
439
- onClose: () => setShowUpgradeModal(false),
440
- featureName: "Route Navigation"
441
- })]
442
- });
443
- }
444
-
445
- // ============================================================================
446
- // Sub-components
447
- // ============================================================================
448
-
449
- function StatItem({
450
- value,
451
- label,
452
- id
453
- }) {
454
- return /*#__PURE__*/_jsxs(View, {
455
- style: styles.statItem,
456
- testID: `buoy-routes-stat-${id}`
457
- // Fabric merges the number and its caption into this container, so a
458
- // stat that isn't spelled out announces as a bare digit.
459
- ,
460
- accessibilityLabel: `${label}, ${value}`,
461
- children: [/*#__PURE__*/_jsx(Text, {
462
- style: styles.statValue,
463
- testID: `buoy-routes-stat-${id}-value`,
464
- children: value
465
- }), /*#__PURE__*/_jsx(Text, {
466
- style: styles.statLabel,
467
- children: label
468
- })]
469
- });
470
- }
471
- function RouteGroupView({
472
- group,
473
- isExpanded,
474
- onToggleExpand,
475
- onNavigate
476
- }) {
477
- return /*#__PURE__*/_jsxs(View, {
478
- style: styles.groupContainer,
479
- children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
480
- style: styles.groupHeader,
481
- onPress: onToggleExpand,
482
- activeOpacity: 0.7,
483
- testID: sitemapGroupTestID(group.title),
484
- accessibilityRole: "button",
485
- accessibilityLabel: `${group.title}, ${group.routes.length} routes`,
486
- children: [/*#__PURE__*/_jsxs(View, {
487
- style: styles.groupHeaderLeft,
488
- children: [isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
489
- size: 14,
490
- color: night.color.textSecondary
491
- }) : /*#__PURE__*/_jsx(ChevronRight, {
492
- size: 14,
493
- color: night.color.textSecondary
494
- }), /*#__PURE__*/_jsx(Text, {
495
- style: styles.groupTitle,
496
- children: group.title
497
- })]
498
- }), /*#__PURE__*/_jsx(View, {
499
- style: styles.groupBadge,
500
- children: /*#__PURE__*/_jsx(Text, {
501
- style: styles.groupCount,
502
- testID: `${sitemapGroupTestID(group.title)}-count`,
503
- children: group.routes.length
504
- })
505
- })]
506
- }), group.description && /*#__PURE__*/_jsx(View, {
507
- style: styles.groupDescription,
508
- children: /*#__PURE__*/_jsx(Text, {
509
- style: styles.groupDescriptionText,
510
- children: group.description
511
- })
512
- }), isExpanded && /*#__PURE__*/_jsx(View, {
513
- style: styles.routesList,
514
- children: group.routes.map((route, index) => /*#__PURE__*/_jsx(RouteItemView, {
515
- route: route,
516
- onNavigate: onNavigate
517
- }, `${route.path}-${index}`))
518
- })]
519
- });
520
- }
521
- function RouteItemView({
522
- route,
523
- depth = 0,
524
- onNavigate
525
- }) {
526
- const [isExpanded, setIsExpanded] = useState(false);
527
- const hasChildren = route.children.length > 0;
528
- const typeColor = getRouteTypeColor(route.type);
529
- const canNavigate = route.type !== "layout" && route.type !== "group";
530
- const testID = sitemapRouteTestID(route.path);
531
-
532
- // Leaf routes (no children) have nothing to drill into — expanding would only
533
- // reveal the Copy/Go actions, so show those inline instead of behind a toggle.
534
- const isExpandable = hasChildren;
535
- const showDetails = isExpandable ? isExpanded : true;
536
- return /*#__PURE__*/_jsx(View, {
537
- style: [styles.routeItem, {
538
- marginLeft: depth * 12
539
- }],
540
- testID: testID,
541
- children: /*#__PURE__*/_jsxs(View, {
542
- style: styles.routeCard,
543
- children: [/*#__PURE__*/_jsxs(View, {
544
- style: styles.routeHeader,
545
- children: [isExpandable ? /*#__PURE__*/_jsxs(TouchableOpacity, {
546
- style: styles.routeHeaderLeft,
547
- onPress: () => setIsExpanded(!isExpanded),
548
- activeOpacity: 0.7,
549
- testID: `${testID}-toggle`,
550
- accessibilityRole: "button",
551
- accessibilityLabel: `${route.path}, ${route.type}, ${route.children.length} nested`,
552
- children: [/*#__PURE__*/_jsx(View, {
553
- style: styles.expandIndicator,
554
- children: isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
555
- size: 14,
556
- color: night.color.textSecondary
557
- }) : /*#__PURE__*/_jsx(ChevronRight, {
558
- size: 14,
559
- color: night.color.textSecondary
560
- })
561
- }), /*#__PURE__*/_jsx(Text, {
562
- style: styles.routePath,
563
- numberOfLines: 3,
564
- testID: `${testID}-path`,
565
- children: route.path
566
- })]
567
- }) :
568
- /*#__PURE__*/
569
- // Non-toggling header for leaf routes (keeps the chevron column as a
570
- // spacer so paths stay aligned with expandable siblings).
571
- _jsxs(View, {
572
- style: styles.routeHeaderLeft
573
- // `accessible` as well as the label: a plain View is not an
574
- // element, so without it the label is never announced and the
575
- // path is read as a bare text node with no type beside it.
576
- ,
577
- accessible: true,
578
- accessibilityLabel: `${route.path}, ${route.type}`,
579
- children: [/*#__PURE__*/_jsx(View, {
580
- style: styles.expandIndicator
581
- }), /*#__PURE__*/_jsx(Text, {
582
- style: styles.routePath,
583
- numberOfLines: 3,
584
- testID: `${testID}-path`,
585
- children: route.path
586
- })]
587
- }), /*#__PURE__*/_jsxs(View, {
588
- style: styles.routeHeaderActions,
589
- children: [hasChildren && /*#__PURE__*/_jsx(View, {
590
- style: styles.childCountBadge,
591
- children: /*#__PURE__*/_jsx(Text, {
592
- style: styles.childCountText,
593
- testID: `${testID}-children-count`,
594
- children: route.children.length
595
- })
596
- }), /*#__PURE__*/_jsx(View, {
597
- style: [styles.typeTag, {
598
- backgroundColor: `${typeColor}15`,
599
- borderColor: `${typeColor}40`
600
- }],
601
- children: /*#__PURE__*/_jsx(Text, {
602
- style: [styles.typeText, {
603
- color: typeColor
604
- }],
605
- testID: `${testID}-type`,
606
- children: route.type.toUpperCase()
607
- })
608
- }), /*#__PURE__*/_jsx(InlineCopyButton, {
609
- value: route.path,
610
- buttonStyle: styles.iconAction,
611
- testID: `${testID}-copy`
612
- }), canNavigate && /*#__PURE__*/_jsx(TouchableOpacity, {
613
- style: styles.goButton,
614
- onPress: () => onNavigate(route),
615
- activeOpacity: 0.7,
616
- testID: `${testID}-go`,
617
- accessibilityRole: "button",
618
- accessibilityLabel: `Go to ${route.path}`,
619
- children: /*#__PURE__*/_jsx(Text, {
620
- style: styles.goButtonText,
621
- children: "Go"
622
- })
623
- })]
624
- })]
625
- }), showDetails && hasChildren && /*#__PURE__*/_jsx(View, {
626
- style: styles.childrenContainer,
627
- children: route.children.map((child, index) => /*#__PURE__*/_jsx(RouteItemView, {
628
- route: child,
629
- depth: depth + 1,
630
- onNavigate: onNavigate
631
- }, `${child.path}-${index}`))
632
- })]
633
- })
634
- });
635
- }
636
-
637
- // ============================================================================
638
- // Helpers
639
- // ============================================================================
640
-
641
- function getRouteTypeColor(type) {
642
- switch (type) {
643
- case "static":
644
- return "#3B82F6";
645
- // Blue
646
- case "dynamic":
647
- return "#F59E0B";
648
- // Orange
649
- case "catch-all":
650
- return "#EC4899";
651
- // Pink
652
- case "index":
653
- return "#10B981";
654
- // Green
655
- case "layout":
656
- return "#8B5CF6";
657
- // Purple
658
- case "group":
659
- return "#6366F1";
660
- // Indigo
661
- case "not-found":
662
- return "#EF4444";
663
- // Red
664
- default:
665
- return "#6B7280";
666
- // Gray
667
- }
668
- }
669
-
670
- // ============================================================================
671
- // Styles
672
- // ============================================================================
673
-
674
- const styles = StyleSheet.create({
675
- container: {
676
- flex: 1,
677
- // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
678
- backgroundColor: "transparent"
679
- },
680
- loadingContainer: {
681
- flex: 1,
682
- justifyContent: "center",
683
- alignItems: "center"
684
- },
685
- loadingText: {
686
- color: night.color.textSecondary,
687
- fontSize: 14,
688
- fontFamily: monoFont,
689
- textAlign: "center",
690
- lineHeight: 20,
691
- paddingHorizontal: 32,
692
- maxWidth: 420
693
- },
694
- header: {
695
- flexDirection: "column",
696
- padding: 8,
697
- borderBottomWidth: 1,
698
- borderBottomColor: night.color.border,
699
- gap: 12,
700
- marginBottom: 8
701
- },
702
- actionsRow: {
703
- flexDirection: "row",
704
- alignItems: "center",
705
- justifyContent: "space-between",
706
- gap: 12,
707
- paddingBottom: 4
708
- },
709
- statsGrid: {
710
- flexDirection: "row",
711
- flexWrap: "wrap",
712
- gap: 8
713
- },
714
- statItem: {
715
- flexGrow: 1,
716
- flexShrink: 1,
717
- flexBasis: "30%",
718
- minWidth: 90,
719
- backgroundColor: night.color.surface,
720
- borderRadius: 8,
721
- paddingVertical: 10,
722
- paddingHorizontal: 12,
723
- alignItems: "center"
724
- },
725
- statValue: {
726
- fontSize: 18,
727
- fontWeight: "800",
728
- color: night.color.text,
729
- fontFamily: monoFont
730
- },
731
- statLabel: {
732
- fontSize: 10,
733
- color: night.color.textTertiary,
734
- marginTop: 4,
735
- fontFamily: monoFont,
736
- textTransform: "uppercase",
737
- letterSpacing: 0.5
738
- },
739
- actionWrapper: {
740
- flex: 1,
741
- alignItems: "center",
742
- justifyContent: "center",
743
- gap: 4,
744
- minWidth: 48
745
- },
746
- actionWrapperDesktop: {
747
- gap: 8,
748
- minWidth: 72
749
- },
750
- actionButtonHeader: {
751
- padding: 6,
752
- borderRadius: 4,
753
- backgroundColor: night.color.surfaceElevated
754
- },
755
- actionButtonHeaderDesktop: {
756
- padding: 12,
757
- borderRadius: 8,
758
- backgroundColor: night.color.surfaceElevated
759
- },
760
- iconButton: {
761
- padding: 6,
762
- borderRadius: 4,
763
- alignItems: "center",
764
- justifyContent: "center"
765
- },
766
- iconButtonDesktop: {
767
- padding: 12,
768
- borderRadius: 8,
769
- backgroundColor: night.color.surfaceElevated
770
- },
771
- actionLabel: {
772
- fontSize: 8,
773
- color: night.color.textTertiary,
774
- fontFamily: monoFont,
775
- textTransform: "uppercase",
776
- letterSpacing: 0.5
777
- },
778
- actionLabelDesktop: {
779
- fontSize: 12
780
- },
781
- refreshButtonDisabled: {
782
- opacity: 0.5
783
- },
784
- metaFooter: {
785
- flexDirection: "row",
786
- alignItems: "center",
787
- justifyContent: "space-between",
788
- gap: 8,
789
- paddingHorizontal: 16,
790
- paddingVertical: 16,
791
- marginTop: 16,
792
- borderTopWidth: 1,
793
- borderTopColor: night.color.border
794
- },
795
- metaText: {
796
- fontSize: 12,
797
- color: night.color.textSecondary,
798
- fontFamily: monoFont
799
- },
800
- sourceBadge: {
801
- paddingHorizontal: 8,
802
- paddingVertical: 2,
803
- borderRadius: 999,
804
- borderWidth: 1,
805
- borderColor: night.color.border,
806
- backgroundColor: night.color.surface
807
- },
808
- sourceText: {
809
- fontSize: 11,
810
- color: night.color.textSecondary,
811
- textTransform: "capitalize",
812
- fontFamily: monoFont
813
- },
814
- searchContainer: {
815
- flexDirection: "row",
816
- alignItems: "center",
817
- padding: 8,
818
- gap: 8,
819
- borderBottomWidth: 1,
820
- borderBottomColor: night.color.border
821
- },
822
- searchInput: {
823
- flex: 1,
824
- color: night.color.text,
825
- fontSize: 14,
826
- fontFamily: monoFont
827
- },
828
- clearButton: {
829
- padding: 4
830
- },
831
- clearButtonText: {
832
- color: night.color.textTertiary,
833
- fontSize: 18
834
- },
835
- closeSearchButton: {
836
- paddingHorizontal: 8,
837
- paddingVertical: 4
838
- },
839
- closeSearchText: {
840
- color: night.color.accent,
841
- fontSize: 14,
842
- fontWeight: "600",
843
- fontFamily: monoFont
844
- },
845
- scrollView: {
846
- flex: 1
847
- },
848
- scrollContent: {
849
- paddingBottom: 8
850
- },
851
- emptyState: {
852
- padding: 32,
853
- alignItems: "center"
854
- },
855
- emptyText: {
856
- color: night.color.textSecondary,
857
- fontSize: 14,
858
- fontFamily: monoFont
859
- },
860
- groupContainer: {
861
- marginVertical: 4
862
- },
863
- groupHeader: {
864
- flexDirection: "row",
865
- alignItems: "center",
866
- justifyContent: "space-between",
867
- padding: 12,
868
- paddingHorizontal: 16,
869
- backgroundColor: night.color.surface,
870
- borderLeftWidth: 3,
871
- borderLeftColor: night.color.fillTertiary
872
- },
873
- groupHeaderLeft: {
874
- flexDirection: "row",
875
- alignItems: "center",
876
- gap: 8,
877
- flex: 1
878
- },
879
- groupTitle: {
880
- fontSize: 12,
881
- fontWeight: "700",
882
- color: night.color.text,
883
- fontFamily: monoFont,
884
- textTransform: "uppercase",
885
- letterSpacing: 0.8
886
- },
887
- groupBadge: {
888
- backgroundColor: "#3B82F615",
889
- borderColor: "#3B82F640",
890
- borderWidth: 1,
891
- borderRadius: 12,
892
- paddingHorizontal: 8,
893
- paddingVertical: 3,
894
- minWidth: 24,
895
- alignItems: "center",
896
- justifyContent: "center"
897
- },
898
- groupCount: {
899
- fontSize: 11,
900
- fontWeight: "600",
901
- color: "#3B82F6",
902
- fontFamily: monoFont
903
- },
904
- routesList: {
905
- // Transparent so the night sheet's star field shows through.
906
- backgroundColor: "transparent"
907
- },
908
- groupDescription: {
909
- paddingHorizontal: 16,
910
- paddingVertical: 8,
911
- marginBottom: 8,
912
- backgroundColor: night.color.surfaceElevated,
913
- borderLeftWidth: 3,
914
- borderLeftColor: night.color.fillTertiary
915
- },
916
- groupDescriptionText: {
917
- fontSize: 11,
918
- color: night.color.textSecondary,
919
- fontFamily: monoFont,
920
- lineHeight: 16
921
- },
922
- routeItem: {
923
- marginBottom: 6
924
- },
925
- routeCard: {
926
- backgroundColor: night.color.surface,
927
- borderRadius: 6,
928
- borderWidth: 1,
929
- borderColor: night.color.border,
930
- overflow: "hidden",
931
- shadowColor: "#000",
932
- shadowOffset: {
933
- width: 0,
934
- height: 1
935
- },
936
- shadowOpacity: 0.05,
937
- shadowRadius: 2,
938
- elevation: 1
939
- },
940
- routeHeader: {
941
- flexDirection: "row",
942
- alignItems: "center",
943
- justifyContent: "space-between",
944
- paddingVertical: 7,
945
- paddingHorizontal: 10,
946
- gap: 8
947
- },
948
- expandIndicator: {
949
- width: 18,
950
- alignItems: "center"
951
- },
952
- routeHeaderLeft: {
953
- flexDirection: "row",
954
- alignItems: "center",
955
- gap: 8,
956
- flex: 1
957
- },
958
- routePath: {
959
- fontSize: 13,
960
- fontWeight: "600",
961
- color: night.color.text,
962
- fontFamily: monoFont,
963
- flex: 1
964
- },
965
- routeHeaderActions: {
966
- flexDirection: "row",
967
- alignItems: "center",
968
- gap: 6
969
- },
970
- childCountBadge: {
971
- backgroundColor: "#6B728015",
972
- borderColor: "#6B728040",
973
- borderWidth: 1,
974
- borderRadius: 12,
975
- paddingHorizontal: 7,
976
- paddingVertical: 2,
977
- minWidth: 22,
978
- alignItems: "center",
979
- justifyContent: "center"
980
- },
981
- childCountText: {
982
- fontSize: 11,
983
- fontWeight: "600",
984
- color: night.color.textSecondary,
985
- fontFamily: monoFont
986
- },
987
- typeTag: {
988
- borderRadius: 4,
989
- paddingHorizontal: 6,
990
- paddingVertical: 2,
991
- borderWidth: 1
992
- },
993
- typeText: {
994
- fontSize: 10,
995
- fontWeight: "600",
996
- fontFamily: monoFont
997
- },
998
- // Compact icon-sized Copy button in the header action cluster.
999
- iconAction: {
1000
- width: 28,
1001
- height: 24,
1002
- borderRadius: 4,
1003
- backgroundColor: night.color.surfaceElevated,
1004
- borderWidth: 1,
1005
- borderColor: night.color.border,
1006
- alignItems: "center",
1007
- justifyContent: "center"
1008
- },
1009
- // Small "Go" pill in the header action cluster.
1010
- goButton: {
1011
- height: 24,
1012
- paddingHorizontal: 10,
1013
- borderRadius: 4,
1014
- alignItems: "center",
1015
- justifyContent: "center",
1016
- backgroundColor: night.color.accentSoft,
1017
- borderWidth: 1,
1018
- borderColor: night.color.accentBorder
1019
- },
1020
- goButtonText: {
1021
- fontSize: 12,
1022
- color: night.color.accent,
1023
- fontFamily: monoFont,
1024
- fontWeight: "600"
1025
- },
1026
- childrenContainer: {
1027
- borderLeftWidth: 2,
1028
- borderLeftColor: night.color.border,
1029
- marginLeft: 16,
1030
- marginRight: 8,
1031
- marginTop: 2,
1032
- marginBottom: 8
1033
- }
1034
- });
3
+ Route: ${a.path}`,[{text:"Cancel",style:"cancel"},{text:s<U.length-1?"Next":"Navigate",onPress:m=>{if(m&&m.trim()){const I={...y,[M]:m.trim()};N(a,s+1,I)}else{T.alert("Invalid Value","Please enter a value for the parameter")}}}],"plain-text")},[L]);const A=w((a,s)=>{if(b){b(a);return}if(!$&&!s?.bypassPro){V(true);return}if(a.type==="layout"||a.type==="group"){T.alert("Cannot Navigate",`${a.type==="layout"?"Layouts":"Route groups"} are not navigable routes`);return}if(a.type==="dynamic"||a.type==="catch-all"){if(a.params.length===0){try{L.push(a.path)}catch(y){T.alert("Navigation Error",String(y))}return}N(a);return}try{L.push(a.path)}catch(y){T.alert("Navigation Error",String(y))}},[L,N,$,b]);const ne=w(()=>{A(Re,{bypassPro:true})},[A]);const ie=w(()=>{if(x)return;v(true);K();setTimeout(()=>v(false),400)},[x,K]);const le=H(()=>{if(!O)return"Awaiting route data";return`Updated ${Se(O)}`},[O]);const se=H(()=>{if(!G)return"Source: unknown";return`Source: ${G}`},[G]);const Q=H(()=>{if(p&&j.length>0){return[{title:"Search Results",icon:"",routes:j}]}return k},[p,j,k]);if(!te){return t(n,{style:[e.container,r],children:t(n,{style:e.loadingContainer,children:t(l,{style:e.loadingText,testID:"buoy-routes-sitemap-loading",children:oe?"Loading routes...":"Route sitemap isn't available on the dashboard \u2014 the route tree lives on the device. Use the Events tab to see navigation here."})})})}return i(n,{style:[e.container,r],children:[z&&i(n,{style:e.searchContainer,testID:"buoy-routes-sitemap-search-field",children:[t(q,{size:16,color:o.color.textTertiary}),t(ue,{style:e.searchInput,placeholder:"Search routes...",placeholderTextColor:o.color.placeholder,value:p,onChangeText:D,autoCapitalize:"none",autoCorrect:false,autoFocus:true,testID:"buoy-routes-sitemap-search",accessibilityLabel:"Search routes"}),p.length>0&&t(C,{onPress:()=>D(""),style:e.clearButton,testID:"buoy-routes-sitemap-search-clear",accessibilityRole:"button",accessibilityLabel:"Clear route search",children:t(l,{style:e.clearButtonText,children:"\u2715"})}),t(C,{style:e.closeSearchButton,onPress:()=>{h(false);D("")},testID:"buoy-routes-sitemap-search-done",accessibilityRole:"button",accessibilityLabel:"Done searching routes",children:t(l,{style:e.closeSearchText,children:"Done"})})]}),i(he,{testID:"buoy-routes-sitemap-scroll",style:e.scrollView,contentContainerStyle:e.scrollContent,children:[!z&&i(n,{style:e.header,children:[i(n,{style:e.actionsRow,children:[i(n,{style:[e.actionWrapper,d&&e.actionWrapperDesktop],children:[t(C,{style:[e.iconButton,d&&e.iconButtonDesktop],onPress:ne,accessibilityLabel:"Go to home route",accessibilityRole:"button",testID:"buoy-routes-sitemap-home",children:t(me,{size:F,color:o.color.textSecondary})}),t(l,{style:[e.actionLabel,d&&e.actionLabelDesktop],children:"Home"})]}),i(n,{style:[e.actionWrapper,d&&e.actionWrapperDesktop],children:[t(xe,{value:re,testID:"buoy-routes-sitemap-copy",size:d?F:14,buttonStyle:d?e.actionButtonHeaderDesktop:e.actionButtonHeader}),t(l,{style:[e.actionLabel,d&&e.actionLabelDesktop],children:"Copy"})]}),i(n,{style:[e.actionWrapper,d&&e.actionWrapperDesktop],children:[t(C,{style:[e.iconButton,d&&e.iconButtonDesktop,x&&e.refreshButtonDisabled],onPress:ie,disabled:x,testID:"buoy-routes-sitemap-refresh",accessibilityRole:"button",accessibilityLabel:"Refresh route tree",children:t(Ce,{size:F,color:x?o.color.textTertiary:o.color.textSecondary})}),t(l,{style:[e.actionLabel,d&&e.actionLabelDesktop],children:"Refresh"})]}),i(n,{style:[e.actionWrapper,d&&e.actionWrapperDesktop],children:[t(C,{style:[e.iconButton,d&&e.iconButtonDesktop],onPress:()=>h(true),testID:"buoy-routes-sitemap-search-open",accessibilityRole:"button",accessibilityLabel:"Search routes",children:t(q,{size:F,color:o.color.textSecondary})}),t(l,{style:[e.actionLabel,d&&e.actionLabelDesktop],children:"Search"})]})]}),i(n,{style:e.statsGrid,children:[t(R,{value:u.total,label:"Total",id:"total"}),t(R,{value:u.static,label:"Static",id:"static"}),t(R,{value:u.dynamic,label:"Dynamic",id:"dynamic"}),t(R,{value:u.catchAll,label:"Catch-All",id:"catch-all"}),t(R,{value:u.layouts,label:"Layouts",id:"layouts"}),t(R,{value:u.groups,label:"Groups",id:"groups"})]})]}),Q.length===0?t(pe,{testID:"buoy-routes-sitemap-empty",otto:"map",title:"No routes found",message:"Nothing in the router matches. Clear the search to see the whole tree."}):Q.map(a=>t(Le,{group:a,isExpanded:p.length>0||W.has(a.title),onToggleExpand:()=>ae(a.title),onNavigate:A},a.title)),i(n,{style:e.metaFooter,children:[t(l,{style:e.metaText,testID:"buoy-routes-sitemap-updated",children:le}),t(n,{style:e.sourceBadge,children:t(l,{style:e.sourceText,testID:"buoy-routes-sitemap-source",children:se})})]})]}),t(De,{visible:P,onClose:()=>V(false),featureName:"Route Navigation"})]})}function R({value:r,label:g,id:f}){return i(n,{style:e.statItem,testID:`buoy-routes-stat-${f}`,accessibilityLabel:`${g}, ${r}`,children:[t(l,{style:e.statValue,testID:`buoy-routes-stat-${f}-value`,children:r}),t(l,{style:e.statLabel,children:g})]})}function Le({group:r,isExpanded:g,onToggleExpand:f,onNavigate:S}){return i(n,{style:e.groupContainer,children:[i(C,{style:e.groupHeader,onPress:f,activeOpacity:.7,testID:Y(r.title),accessibilityRole:"button",accessibilityLabel:`${r.title}, ${r.routes.length} routes`,children:[i(n,{style:e.groupHeaderLeft,children:[g?t(J,{size:14,color:o.color.textSecondary}):t(X,{size:14,color:o.color.textSecondary}),t(l,{style:e.groupTitle,children:r.title})]}),t(n,{style:e.groupBadge,children:t(l,{style:e.groupCount,testID:`${Y(r.title)}-count`,children:r.routes.length})})]}),r.description&&t(n,{style:e.groupDescription,children:t(l,{style:e.groupDescriptionText,children:r.description})}),g&&t(n,{style:e.routesList,children:r.routes.map((b,p)=>t(ee,{route:b,onNavigate:S},`${b.path}-${p}`))})]})}function ee({route:r,depth:g=0,onNavigate:f}){const[S,b]=B(false);const p=r.children.length>0;const D=we(r.type);const z=r.type!=="layout"&&r.type!=="group";const h=Te(r.path);const W=p;const E=W?S:true;return t(n,{style:[e.routeItem,{marginLeft:g*12}],testID:h,children:i(n,{style:e.routeCard,children:[i(n,{style:e.routeHeader,children:[W?i(C,{style:e.routeHeaderLeft,onPress:()=>b(!S),activeOpacity:.7,testID:`${h}-toggle`,accessibilityRole:"button",accessibilityLabel:`${r.path}, ${r.type}, ${r.children.length} nested`,children:[t(n,{style:e.expandIndicator,children:S?t(J,{size:14,color:o.color.textSecondary}):t(X,{size:14,color:o.color.textSecondary})}),t(l,{style:e.routePath,numberOfLines:3,testID:`${h}-path`,children:r.path})]}):i(n,{style:e.routeHeaderLeft,accessible:true,accessibilityLabel:`${r.path}, ${r.type}`,children:[t(n,{style:e.expandIndicator}),t(l,{style:e.routePath,numberOfLines:3,testID:`${h}-path`,children:r.path})]}),i(n,{style:e.routeHeaderActions,children:[p&&t(n,{style:e.childCountBadge,children:t(l,{style:e.childCountText,testID:`${h}-children-count`,children:r.children.length})}),t(n,{style:[e.typeTag,{backgroundColor:`${D}15`,borderColor:`${D}40`}],children:t(l,{style:[e.typeText,{color:D}],testID:`${h}-type`,children:r.type.toUpperCase()})}),t(be,{value:r.path,buttonStyle:e.iconAction,testID:`${h}-copy`}),z&&t(C,{style:e.goButton,onPress:()=>f(r),activeOpacity:.7,testID:`${h}-go`,accessibilityRole:"button",accessibilityLabel:`Go to ${r.path}`,children:t(l,{style:e.goButtonText,children:"Go"})})]})]}),E&&p&&t(n,{style:e.childrenContainer,children:r.children.map((x,v)=>t(ee,{route:x,depth:g+1,onNavigate:f},`${x.path}-${v}`))})]})})}function we(r){switch(r){case"static":return"#3B82F6";case"dynamic":return"#F59E0B";case"catch-all":return"#EC4899";case"index":return"#10B981";case"layout":return"#8B5CF6";case"group":return"#6366F1";case"not-found":return"#EF4444";default:return"#6B7280"}}const e=ye.create({container:{flex:1,backgroundColor:"transparent"},loadingContainer:{flex:1,justifyContent:"center",alignItems:"center"},loadingText:{color:o.color.textSecondary,fontSize:14,fontFamily:c,textAlign:"center",lineHeight:20,paddingHorizontal:32,maxWidth:420},header:{flexDirection:"column",padding:8,borderBottomWidth:1,borderBottomColor:o.color.border,gap:12,marginBottom:8},actionsRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:12,paddingBottom:4},statsGrid:{flexDirection:"row",flexWrap:"wrap",gap:8},statItem:{flexGrow:1,flexShrink:1,flexBasis:"30%",minWidth:90,backgroundColor:o.color.surface,borderRadius:8,paddingVertical:10,paddingHorizontal:12,alignItems:"center"},statValue:{fontSize:18,fontWeight:"800",color:o.color.text,fontFamily:c},statLabel:{fontSize:10,color:o.color.textTertiary,marginTop:4,fontFamily:c,textTransform:"uppercase",letterSpacing:.5},actionWrapper:{flex:1,alignItems:"center",justifyContent:"center",gap:4,minWidth:48},actionWrapperDesktop:{gap:8,minWidth:72},actionButtonHeader:{padding:6,borderRadius:4,backgroundColor:o.color.surfaceElevated},actionButtonHeaderDesktop:{padding:12,borderRadius:8,backgroundColor:o.color.surfaceElevated},iconButton:{padding:6,borderRadius:4,alignItems:"center",justifyContent:"center"},iconButtonDesktop:{padding:12,borderRadius:8,backgroundColor:o.color.surfaceElevated},actionLabel:{fontSize:8,color:o.color.textTertiary,fontFamily:c,textTransform:"uppercase",letterSpacing:.5},actionLabelDesktop:{fontSize:12},refreshButtonDisabled:{opacity:.5},metaFooter:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",gap:8,paddingHorizontal:16,paddingVertical:16,marginTop:16,borderTopWidth:1,borderTopColor:o.color.border},metaText:{fontSize:12,color:o.color.textSecondary,fontFamily:c},sourceBadge:{paddingHorizontal:8,paddingVertical:2,borderRadius:999,borderWidth:1,borderColor:o.color.border,backgroundColor:o.color.surface},sourceText:{fontSize:11,color:o.color.textSecondary,textTransform:"capitalize",fontFamily:c},searchContainer:{flexDirection:"row",alignItems:"center",padding:8,gap:8,borderBottomWidth:1,borderBottomColor:o.color.border},searchInput:{flex:1,color:o.color.text,fontSize:14,fontFamily:c},clearButton:{padding:4},clearButtonText:{color:o.color.textTertiary,fontSize:18},closeSearchButton:{paddingHorizontal:8,paddingVertical:4},closeSearchText:{color:o.color.accent,fontSize:14,fontWeight:"600",fontFamily:c},scrollView:{flex:1},scrollContent:{paddingBottom:8},emptyState:{padding:32,alignItems:"center"},emptyText:{color:o.color.textSecondary,fontSize:14,fontFamily:c},groupContainer:{marginVertical:4},groupHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",padding:12,paddingHorizontal:16,backgroundColor:o.color.surface,borderLeftWidth:3,borderLeftColor:o.color.fillTertiary},groupHeaderLeft:{flexDirection:"row",alignItems:"center",gap:8,flex:1},groupTitle:{fontSize:12,fontWeight:"700",color:o.color.text,fontFamily:c,textTransform:"uppercase",letterSpacing:.8},groupBadge:{backgroundColor:"#3B82F615",borderColor:"#3B82F640",borderWidth:1,borderRadius:12,paddingHorizontal:8,paddingVertical:3,minWidth:24,alignItems:"center",justifyContent:"center"},groupCount:{fontSize:11,fontWeight:"600",color:"#3B82F6",fontFamily:c},routesList:{backgroundColor:"transparent"},groupDescription:{paddingHorizontal:16,paddingVertical:8,marginBottom:8,backgroundColor:o.color.surfaceElevated,borderLeftWidth:3,borderLeftColor:o.color.fillTertiary},groupDescriptionText:{fontSize:11,color:o.color.textSecondary,fontFamily:c,lineHeight:16},routeItem:{marginBottom:6},routeCard:{backgroundColor:o.color.surface,borderRadius:6,borderWidth:1,borderColor:o.color.border,overflow:"hidden",shadowColor:"#000",shadowOffset:{width:0,height:1},shadowOpacity:.05,shadowRadius:2,elevation:1},routeHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingVertical:7,paddingHorizontal:10,gap:8},expandIndicator:{width:18,alignItems:"center"},routeHeaderLeft:{flexDirection:"row",alignItems:"center",gap:8,flex:1},routePath:{fontSize:13,fontWeight:"600",color:o.color.text,fontFamily:c,flex:1},routeHeaderActions:{flexDirection:"row",alignItems:"center",gap:6},childCountBadge:{backgroundColor:"#6B728015",borderColor:"#6B728040",borderWidth:1,borderRadius:12,paddingHorizontal:7,paddingVertical:2,minWidth:22,alignItems:"center",justifyContent:"center"},childCountText:{fontSize:11,fontWeight:"600",color:o.color.textSecondary,fontFamily:c},typeTag:{borderRadius:4,paddingHorizontal:6,paddingVertical:2,borderWidth:1},typeText:{fontSize:10,fontWeight:"600",fontFamily:c},iconAction:{width:28,height:24,borderRadius:4,backgroundColor:o.color.surfaceElevated,borderWidth:1,borderColor:o.color.border,alignItems:"center",justifyContent:"center"},goButton:{height:24,paddingHorizontal:10,borderRadius:4,alignItems:"center",justifyContent:"center",backgroundColor:o.color.accentSoft,borderWidth:1,borderColor:o.color.accentBorder},goButtonText:{fontSize:12,color:o.color.accent,fontFamily:c,fontWeight:"600"},childrenContainer:{borderLeftWidth:2,borderLeftColor:o.color.border,marginLeft:16,marginRight:8,marginTop:2,marginBottom:8}});