--- name: page-and-route description: Helps create and configure pages and routes in the OneKey app-monorepo. Use when creating new pages, configuring routes, setting up deep links, handling QR codes, or understanding navigation patterns. Page, route, navigation, deep link, universal link, QR code, modal, tab, onboarding. --- # Page and Route Skill This skill helps create and configure pages and routes in the OneKey app-monorepo. --- ## ⚠️ WARNING: Page Deletion Policy **DO NOT DELETE PAGES unless you have confirmed there are NO external links to the page.** External links include: - Deep links / Universal links - QR code handlers - Banner click handlers - Web URLs shared externally - Third-party integrations - Marketing materials / Documentation **If you need to remove a page's functionality:** Instead of deleting the route, keep the route registered and modify the page component: ```typescript import { Page } from '@onekeyhq/components'; /** * @deprecated This page has been deprecated since v5.0.0 * * New location: packages/kit/src/views/NewFeature/pages/NewPage.tsx * New route: EModalRoutes.NewFeatureModal -> ENewFeatureRoutes.NewPage * * This page is kept for backward compatibility with external deep links. * DO NOT DELETE - external links may still reference this route. */ function DeprecatedPage() { return ( true} // Always redirect onRedirected={() => { // Navigate to replacement page or home navigation.switchTab(ETabRoutes.Home); // Or show a toast explaining the change Toast.info({ title: 'This feature has been moved' }); }} > {null} // Render nothing ); } export default DeprecatedPage; ``` This approach ensures: - Users accessing via old deep links are gracefully redirected - No broken links or blank screens - Analytics can track deprecated route access - Backward compatibility is maintained --- ## Page Types The page types are defined in `packages/components/src/hocs/PageType/pageType.ts`: | Type | Description | Animation Behavior | |------|-------------|-------------------| | `modal` | Modal route pages | iOS: First page slides from bottom, subsequent pages slide from right. Android: No animation. Web/Desktop/Extension/iPad: Same as iOS but displays as floating popup | | `stack` | Tab route pages (will be renamed from "stack") | On small screens, first page shows in bottom tab bar; on large screens, tab shows in sidebar. Subsequent pages slide from right. Android: No animation | | `fullScreen` | Full screen overlay (deprecated) | Layer is lower than modal, avoid adding new pages | | `onboarding` | OnboardingV2 full screen pages | Full screen overlay on all platforms, layer is below modal | ## Creating Routes ### 1. Modal Route Modal routes are configured in `packages/kit/src/routes/Modal/router.tsx`. **Example: Creating a NotificationsModal route** #### Step 1: Define Route Enum and Param Types Create route file `packages/shared/src/routes/notifications.ts`: ```typescript export enum EModalNotificationsRoutes { NotificationList = 'NotificationList', NotificationIntroduction = 'NotificationIntroduction', } export type IModalNotificationsParamList = { [EModalNotificationsRoutes.NotificationList]: undefined; [EModalNotificationsRoutes.NotificationIntroduction]: undefined; }; ``` #### Step 2: Add to EModalRoutes Enum In `packages/shared/src/routes/modal.ts`: ```typescript export enum EModalRoutes { // ... existing routes NotificationsModal = 'NotificationsModal', } export type IModalParamList = { // ... existing types [EModalRoutes.NotificationsModal]: IModalNotificationsParamList; }; ``` #### Step 3: Create Router Configuration Create `packages/kit/src/views/Notifications/router/index.ts`: ```typescript import type { IModalFlowNavigatorConfig } from '@onekeyhq/components'; import { LazyLoadPage } from '@onekeyhq/kit/src/components/LazyLoadPage'; import type { IModalNotificationsParamList } from '@onekeyhq/shared/src/routes/notifications'; import { EModalNotificationsRoutes } from '@onekeyhq/shared/src/routes/notifications'; const NotificationList = LazyLoadPage( () => import('@onekeyhq/kit/src/views/Notifications/pages/NotificationList'), ); const NotificationIntroduction = LazyLoadPage( () => import('@onekeyhq/kit/src/views/Notifications/pages/NotificationIntroduction'), ); export const ModalNotificationsRouter: IModalFlowNavigatorConfig< EModalNotificationsRoutes, IModalNotificationsParamList >[] = [ { name: EModalNotificationsRoutes.NotificationList, component: NotificationList, }, { name: EModalNotificationsRoutes.NotificationIntroduction, component: NotificationIntroduction, }, ]; ``` #### Step 4: Register in Modal Router In `packages/kit/src/routes/Modal/router.tsx`: ```typescript import { ModalNotificationsRouter } from '../../views/Notifications/router'; const router: IModalRootNavigatorConfig[] = [ // ... existing routes { name: EModalRoutes.NotificationsModal, children: ModalNotificationsRouter, }, ]; ``` #### Step 5: Create Page Component Create `packages/kit/src/views/Notifications/pages/NotificationList.tsx`: ```typescript import { Page } from '@onekeyhq/components'; function NotificationList() { return ( {/* Page content */} ); } export default NotificationList; ``` ### 2. Onboarding Route Onboarding routes are configured in `packages/kit/src/views/Onboardingv2/router/index.tsx`. **Example: Creating AddExistingWallet page** #### Step 1: Define Route Enum In `packages/shared/src/routes/onboarding.ts`: ```typescript export enum EOnboardingPagesV2 { GetStarted = 'GetStarted', AddExistingWallet = 'AddExistingWallet', // ... more pages } ``` #### Step 2: Add to Router In `packages/kit/src/views/Onboardingv2/router/index.tsx`: ```typescript const AddExistingWallet = LazyLoadPage( () => import('../pages/AddExistingWallet'), undefined, false, , ); export const OnboardingRouterV2: IModalFlowNavigatorConfig< EOnboardingPagesV2, IOnboardingParamListV2 >[] = [ // ... other routes { name: EOnboardingPagesV2.AddExistingWallet, component: AddExistingWallet, options: hiddenHeaderOptions, }, ]; ``` ### 3. Tab Route Tab routes are configured in `packages/kit/src/routes/Tab/router.ts`. **Example: Creating Market tab with sub-pages** #### Step 1: Define Route Enum and Params In `packages/shared/src/routes/tabMarket.ts`: ```typescript export enum ETabMarketRoutes { TabMarket = 'TabMarket', MarketDetail = 'MarketDetail', MarketDetailV2 = 'MarketDetailV2', MarketNativeDetail = 'MarketNativeDetail', MarketBannerDetail = 'MarketBannerDetail', } export type ITabMarketParamList = { [ETabMarketRoutes.TabMarket]: { from?: EEnterWay } | undefined; [ETabMarketRoutes.MarketDetail]: { token: string }; [ETabMarketRoutes.MarketDetailV2]: { tokenAddress: string; network: string; isNative?: boolean; }; // ... more params }; ``` #### Step 2: Create Sub-Router Create `packages/kit/src/routes/Tab/Marktet/router.ts`: ```typescript import type { ITabSubNavigatorConfig } from '@onekeyhq/components'; import { ETabMarketRoutes } from '@onekeyhq/shared/src/routes'; import { LazyLoadRootTabPage, LazyLoadPage } from '../../../components/LazyLoadPage'; const MarketHome = LazyLoadRootTabPage(() => import('../../../views/Market/MarketHome')); const MarketDetail = LazyLoadPage(() => import('../../../views/Market/MarketDetail')); export const marketRouters: ITabSubNavigatorConfig[] = [ { rewrite: '/', name: ETabMarketRoutes.TabMarket, headerShown: !platformEnv.isNative, component: MarketHome, }, { name: ETabMarketRoutes.MarketDetail, component: MarketDetail, rewrite: '/tokens/:token', }, ]; ``` #### Step 3: Register in Tab Router In `packages/kit/src/routes/Tab/router.ts`: ```typescript import { marketRouters } from './Marktet/router'; export const useTabRouterConfig = () => { return useMemo(() => { const tabs = [ { name: ETabRoutes.Market, tabBarIcon: (focused?: boolean) => focused ? 'ChartTrendingUp2Solid' : 'ChartTrendingUp2Outline', translationId: ETranslations.global_market, rewrite: '/market', exact: true, children: marketRouters, // Control visibility per platform hiddenIcon: platformEnv.isNative, // Hide on mobile }, // ... other tabs ]; return tabs; }, []); }; ``` **Tab Configuration Options:** - `hiddenIcon`: Hide tab icon on certain platforms - `hideOnTabBar`: Hide from tab bar but keep route accessible - `inMoreAction`: Show in "More" menu instead of main tab bar - `freezeOnBlur`: Keep tab state when switching tabs ## Configuring Page Paths ### ⚠️ WARNING: Route Paths Must Be Unique **Route paths MUST NOT be duplicated.** Each route path must be unique across the entire application. If duplicate paths are detected, the application will throw an error at startup: ``` Found conflicting screens with the same pattern. The pattern '/market/tokens/.' resolves to both 'Main > Market > MarketDetail' and 'Main > Market > MarketDetailV2'. Patterns must be unique and cannot resolve to more than one screen. ``` **Common causes of duplicate paths:** - Two routes using the same `rewrite` value - Forgetting to update path when copying route configuration - Child routes with identical path segments **How to avoid:** - Always use unique `rewrite` values for each route - When adding similar routes, differentiate paths (e.g., `/token/:network` vs `/token/:network/:tokenAddress`) - Run the app locally to verify no path conflicts before committing ### URL Path Configuration Route paths are calculated in `packages/kit/src/routes/config/index.ts` via `resolveScreens`. #### resolveScreens Function ```typescript interface IScreenRouterConfig { name: string; // Route name (used as default path segment) rewrite?: string; // Override path segment exact?: boolean; // If true, ignores parent paths and uses only rewrite children?: IScreenRouterConfig[]; } // resolveScreens transforms route config into path config const resolveScreens = (routes: IScreenRouterConfig[]) => routes.reduce((prev, route) => { prev[route.name] = { path: route.rewrite ? route.rewrite : route.name, // Use rewrite or name as path exact: !!route.exact, }; if (route.children) { prev[route.name].screens = resolveScreens(route.children); // Recursive for children } return prev; }, {}); ``` **How it works:** 1. Each route's `name` becomes its key in the config 2. The `path` is either `rewrite` value (if provided) or `name` 3. `exact` flag determines if parent paths are included 4. Children routes are processed recursively #### Path Calculation Rules **Rule 1: Default path accumulation** Without any configuration, paths accumulate based on route hierarchy: ``` Route: Main -> Market -> MarketDetail Path: /Main/Market/MarketDetail ``` **Rule 2: `rewrite` replaces current segment only** ```typescript // Tab router config { name: ETabRoutes.Market, rewrite: '/market', // Replaces 'Market' with 'market' children: marketRouters, } // Result: /Main/market/... (only current segment changed) ``` **Rule 3: `exact: true` truncates all parent paths** ```typescript { name: EModalRoutes.SettingModal, children: ModalSettingStack, rewrite: '/settings', exact: true, // Ignores /Modal prefix } // Without exact: /Modal/settings/... // With exact: /settings/... ``` **Rule 4: Path params with `/:param` syntax** ```typescript // In marketRouters { name: ETabMarketRoutes.MarketDetail, component: MarketDetail, rewrite: '/tokens/:token', // :token is a path parameter } // Result path pattern: /market/tokens/:token // Actual URL example: /market/tokens/bitcoin ``` #### Complete Examples **Example 1: Tab Route with rewrite + exact** ```typescript // Tab router (packages/kit/src/routes/Tab/router.ts) { name: ETabRoutes.Market, rewrite: '/market', exact: true, // Path starts from /market, not /Main/Market children: marketRouters, } // Sub-router (packages/kit/src/routes/Tab/Marktet/router.ts) export const marketRouters = [ { name: ETabMarketRoutes.TabMarket, rewrite: '/', // Root of /market component: MarketHome, }, { name: ETabMarketRoutes.MarketDetail, rewrite: '/tokens/:token', component: MarketDetail, }, ]; // Resulting paths: // TabMarket: /market/ // MarketDetail: /market/tokens/:token (e.g., /market/tokens/bitcoin) ``` **Example 2: Modal Route with exact** ```typescript // Modal router (packages/kit/src/routes/Modal/router.tsx) { name: EModalRoutes.SettingModal, children: ModalSettingStack, rewrite: '/settings', exact: true, } // Resulting path: /settings/... (not /Modal/settings/...) ``` **Example 3: Onboarding Route** ```typescript // Onboarding router config { name: EOnboardingV2Routes.OnboardingV2, rewrite: '/onboarding', exact: true, children: OnboardingRouterV2, } // Sub-routes { name: EOnboardingPagesV2.GetStarted, rewrite: '/get-started', component: GetStarted, } // Resulting path: /onboarding/get-started ``` **Example 4: Native token detail (no tokenAddress in path)** ```typescript { name: ETabMarketRoutes.MarketNativeDetail, component: MarketDetailV2, rewrite: '/token/:network', // Only network, no token address } // URL example: /market/token/eth ``` **Example 5: Non-native token detail (with tokenAddress)** ```typescript { name: ETabMarketRoutes.MarketDetailV2, component: MarketDetailV2, rewrite: '/token/:network/:tokenAddress', } // URL example: /market/token/eth/0x1234... ``` #### Path Calculation in buildAllowList The `pagePath` template function in `routeUtils.ts` calculates full paths: ```typescript function pagePath(_: TemplateStringsArray, ...screenNames: string[]): string { const path = screenNames.reduce((prev, screenName) => { const screen = screenConfig[screenName]; const paths = screen.path.split('/:'); const rawPath = removeExtraSlash(paths[0]); const screenPath = paths.length > 1 ? `${rawPath}/.` : rawPath; // If exact, use only current path; otherwise, accumulate return screen.exact ? screenPath : addPath(prev, screenPath); }, ''); return `/${path}`; } // Usage: pagePath`${ERootRoutes.Main}${ETabRoutes.Market}${ETabMarketRoutes.MarketDetail}` // Result: /market/tokens/. (the /. indicates path params follow) ``` ### URL Allow List (Browser Display) By default, routes don't display in the browser URL bar. To enable URL display, configure in `packages/shared/src/utils/routeUtils.ts`: ```typescript export const buildAllowList = (screens, perpDisabled, perpTabShowWeb) => { const rules = { // Show URL with parameters [pagePath`${ERootRoutes.Main}${ETabRoutes.Market}${ETabMarketRoutes.MarketDetailV2}`]: { showUrl: true, // Enable URL display showParams: true, // Show query parameters }, // Show URL without parameters [pagePath`${ERootRoutes.Main}${ETabRoutes.Swap}${ETabSwapRoutes.TabSwap}`]: { showUrl: true, showParams: false, // Hide query parameters }, }; return rules; }; ``` **Note:** When adding new pages that need browser URL display, configure them in `buildAllowList`. ### Platform-Specific Routing: Web vs Extension The routing implementation differs between web/native platforms and browser extension: #### Web/Native: Standard Path Routing File: `packages/kit/src/routes/config/getStateFromPath.ts` ```typescript // Simply re-exports from React Navigation export { getStateFromPath } from '@react-navigation/core'; ``` **URL format:** `https://app.onekey.so/market/tokens/bitcoin` #### Extension: Hash Routing File: `packages/kit/src/routes/config/getStateFromPath.ext.ts` The extension uses a **custom implementation** copied from `@react-navigation/core` with hash routing modifications. The file is marked with `// ---CHANGED Begin----` and `// ---CHANGED end----` comments to indicate customizations. **Why custom implementation is needed:** - Browser extensions load from local HTML files (e.g., `ui-expand-tab.html`) - Standard path routing doesn't work with extension URL scheme - Hash routing allows navigation without page reload **Key customizations in `getStateFromPath.ext.ts`:** ```typescript // 1. Store initial pathname for later use (line 98-100) // ---CHANGED Begin----: initial path from url let initialPath: string | undefined = globalThis.location.pathname; // ---CHANGED end---- // 2. Extract path from hash instead of pathname (line 107-109) export function getStateFromPath( path: string, options?: Options ): ResultState | undefined { // ---CHANGED Begin----: support hash router path = globalThis.location.hash.split('#').pop() || '/'; // ---CHANGED end---- // ... rest of function } // 3. Rewrite initial path for focused route (line 731-736) // In createNestedStateObject function: route = findFocusedRoute(state) as ParsedRoute; // ---CHANGED Begin----: rewrite hash path to initial path if (initialPath) { route.path = initialPath; initialPath = undefined; } // ---CHANGED end---- ``` **URL format returned (in `index.ts` line 139):** ```typescript return `${extHtmlFileUrl}#${newPath}`; // Example: /ui-expand-tab.html#/market/tokens/bitcoin ``` **URL format comparison:** | Platform | URL Format | |----------|------------| | Web | `https://app.onekey.so/market/tokens/bitcoin` | | Extension | `chrome-extension://xxx/ui-expand-tab.html#/market/tokens/bitcoin` | **Important notes for extension routing:** 1. The `#` symbol separates the HTML file from the route path 2. Windows Chrome has specific requirements (line 131-137 in index.ts): ```typescript // /ui-expand-tab.html/#/ NOT working for Windows Chrome // /ui-expand-tab.html#/ works fine ``` 3. When developing extension-specific routes, test on both Mac and Windows Chrome #### Extension Routing Modifications Summary The `getStateFromPath.ext.ts` file contains 4 key modifications from the original `@react-navigation/core` implementation: | # | Location | Modification | Functional Impact | |---|----------|--------------|-------------------| | 1 | Lines 33-37 | Import changes | Uses `OneKeyLocalError` instead of native Error; imports utilities from `@react-navigation/core` | | 2 | Lines 98-100 | `let initialPath = globalThis.location.pathname` | Stores HTML file path (e.g., `/ui-expand-tab.html`) for later use in route state | | 3 | Lines 107-109 | `path = globalThis.location.hash.split('#').pop()` | **Core change**: Extracts route path from URL hash instead of using passed parameter | | 4 | Lines 731-736 | Rewrite `route.path` with `initialPath` | Sets focused route's path to initial HTML file path for proper state management | **Why these modifications are necessary:** - Browser extensions run from local HTML files, not a web server - Standard path routing (`/market/tokens/btc`) doesn't work with `chrome-extension://` URLs - Hash routing (`#/market/tokens/btc`) allows client-side navigation without server requests - The initial path storage ensures the extension knows its base HTML file location #### Route Path Output by Platform In `packages/kit/src/routes/config/index.ts`, the `getPathFromState` function handles platform differences: ```typescript getPathFromState(state, options) { // ... calculate path ... if (platformEnv.isExtension) { // Extension: return hash-based URL if (newPath === '/' && globalThis.location.href.endsWith('#/')) { return extHtmlFileUrl; // e.g., /ui-expand-tab.html } return `${extHtmlFileUrl}#${newPath}`; // e.g., /ui-expand-tab.html#/market } // Web/Desktop: return standard path return newPath; // e.g., /market } ``` ## Page Lifecycle The `Page` component supports lifecycle callbacks defined in `packages/components/src/layouts/Page/type.ts`: ```typescript interface IPageLifeCycle { onMounted?: () => void; // Called after page transition completes onUnmounted?: () => void; // Called after page unmount transition completes onCancel?: () => void; // Called when page closed without confirm onConfirm?: () => void; // Called when page closed with confirm onClose?: (extra?: { flag?: string }) => void; // Called on any close onRedirected?: () => void; // Called after redirect completes shouldRedirect?: () => boolean; // Return true to redirect immediately } ``` **Usage in Page Component:** ```typescript import { Page } from '@onekeyhq/components'; function MyPage() { return ( console.log('Page mounted')} onUnmounted={() => console.log('Page unmounted')} shouldRedirect={() => !hasPermission} onRedirected={() => navigation.navigate('Login')} > {/* content */} ); } ``` **Use Cases:** | Callback | Use Case | |----------|----------| | `onMounted` | Load data, start animations, track page views | | `onUnmounted` | Cleanup resources, cancel requests | | `onClose` | Save draft, confirm unsaved changes | | `onCancel` | Track cancel actions, cleanup form state | | `onConfirm` | Track confirm actions, submit data | | `shouldRedirect` | Auth guards, permission checks | | `onRedirected` | Navigate to login, show toast | **Redirect Pattern:** ```typescript { // Return true to immediately go back and trigger onRedirected return !isUserAuthenticated; }} onRedirected={() => { // Called after going back, navigate to appropriate screen navigation.pushModal(EModalRoutes.OnboardingModal, { screen: EOnboardingPages.Login, }); }} > ``` ## Deep Link / Universal Link Configuration Configure in `packages/kit/src/routes/config/deeplink/index.ts`: **Example: Adding invite_share deep link** #### Step 1: Define Deep Link Path In `packages/shared/src/consts/deeplinkConsts.tsx`: ```typescript export enum EOneKeyDeepLinkPath { url_account = 'url_account', market_detail = 'market_detail', invite_share = 'invite_share', // Add new path } export type IEOneKeyDeepLinkParams = { [EOneKeyDeepLinkPath.invite_share]: { utm_source: string; code: string; }; }; ``` #### Step 2: Handle Deep Link In `packages/kit/src/routes/config/deeplink/index.ts`: ```typescript async function processDeepLinkUrlAccount(params, times = 0) { const { parsedUrl } = params; const { hostname, queryParams, scheme, path } = parsedUrl; if (scheme === ONEKEY_APP_DEEP_LINK || scheme === ONEKEY_APP_DEEP_LINK_NAME) { switch (hostname ?? path?.slice(1)) { case EOneKeyDeepLinkPath.invite_share: { const { utm_source: utmSource, code } = queryParams as IEOneKeyDeepLinkParams[EOneKeyDeepLinkPath.invite_share]; if (navigation) { navigation.switchTab(ETabRoutes.ReferFriends, { screen: ETabReferFriendsRoutes.TabReferAFriend, params: { utmSource, code }, }); } break; } } } } ``` **Deep Link Format:** - `onekey-wallet://invite_share?utm_source=twitter&code=ABC123` - `https://app.onekey.so/wc/connect/wc?uri=...` (Universal Link) ## QR Code / Banner Click Navigation Configure QR code handlers for scanning and banner clicks. **Example: MARKET_DETAIL handler** #### Step 1: Define Handler Type In `packages/shared/types/qrCode.ts`: ```typescript export enum EQRCodeHandlerType { MARKET_DETAIL = 'MARKET_DETAIL', // ... other types } export enum EQRCodeHandlerNames { marketDetail = 'marketDetail', // ... other names } export const PARSE_HANDLER_NAMES = { all: [ EQRCodeHandlerNames.marketDetail, // ... other handlers ], }; ``` #### Step 2: Create Handler Create `packages/kit-bg/src/services/ServiceScanQRCode/utils/parseQRCode/handlers/marketDetail.ts`: ```typescript import { WEB_APP_URL, WEB_APP_URL_DEV, WEB_APP_URL_SHORT } from '@onekeyhq/shared/src/config/appConfig'; import { EQRCodeHandlerType } from '@onekeyhq/shared/types/qrCode'; import type { IMarketDetailValue, IQRCodeHandler } from '../type'; const marketDetail: IQRCodeHandler = async (value, options) => { const urlValue = options?.urlResult; if (urlValue?.data?.urlParamList) { const origin = urlValue?.data?.origin; // Check if URL matches OneKey web app if ( [WEB_APP_URL, WEB_APP_URL_DEV, WEB_APP_URL_SHORT].includes(origin) && urlValue?.data?.pathname.startsWith('/market/tokens/') ) { const coinGeckoId = urlValue?.data?.pathname.split('/market/tokens/').pop(); return { type: EQRCodeHandlerType.MARKET_DETAIL, data: { origin, coinGeckoId }, }; } } return null; }; export default marketDetail; ``` #### Step 3: Register Handler In `packages/kit-bg/src/services/ServiceScanQRCode/utils/parseQRCode/handlers/index.ts`: ```typescript import marketDetail from './marketDetail'; export const PARSE_HANDLERS = { [EQRCodeHandlerNames.marketDetail]: marketDetail, // ... other handlers }; ``` #### Step 4: Handle Result in UI In `packages/kit/src/views/ScanQrCode/hooks/useParseQRCode.tsx`: ```typescript const parse = useCallback(async (value, params) => { const result = await backgroundApiProxy.serviceScanQRCode.parse(value, options); switch (result.type) { case EQRCodeHandlerType.MARKET_DETAIL: { const { coinGeckoId } = result.data as IMarketDetailValue; if (coinGeckoId) { await closeScanPage(); void marketNavigation.pushDetailPageFromDeeplink(navigation, { coinGeckoId }); } break; } } }, [navigation]); ``` ## Navigation Methods ```typescript // Push modal navigation.pushModal(EModalRoutes.NotificationsModal, { screen: EModalNotificationsRoutes.NotificationList, params: { /* page params */ }, }); // Switch tab navigation.switchTab(ETabRoutes.Market); // Navigate within tab navigation.navigate(ERootRoutes.Main, { screen: ETabRoutes.Market, params: { screen: ETabMarketRoutes.MarketDetail, params: { token: 'bitcoin' }, }, }, { pop: true, }); // Go back navigation.goBack(); ``` ### ⚠️ IMPORTANT: Always Use `pop: true` with `navigation.navigate` When using `navigation.navigate`, **ALWAYS** include the `pop: true` option to ensure proper navigation stack management: ```typescript // ✅ Correct - with pop: true navigation.navigate(ERootRoutes.Main, undefined, { pop: true, }); navigation.navigate(ERootRoutes.Main, { screen: ETabRoutes.Market, params: { screen: ETabMarketRoutes.MarketDetail, params: { token: 'bitcoin' }, }, }, { pop: true, }); // ❌ Wrong - missing pop: true navigation.navigate(ERootRoutes.Main, { screen: ETabRoutes.Market, }); ``` **Why `pop: true` is required:** - Prevents navigation stack from growing indefinitely - Ensures proper back button behavior - Avoids memory leaks from accumulated screens - Maintains consistent navigation state across platforms ## Files Reference | Purpose | Location | |---------|----------| | Page type enum | `packages/components/src/hocs/PageType/pageType.ts` | | Modal routes | `packages/kit/src/routes/Modal/router.tsx` | | Tab routes | `packages/kit/src/routes/Tab/router.ts` | | Onboarding routes | `packages/kit/src/views/Onboardingv2/router/index.tsx` | | Route enums | `packages/shared/src/routes/` | | URL allow list | `packages/shared/src/utils/routeUtils.ts` | | Route config (linking) | `packages/kit/src/routes/config/index.ts` | | Web/Native path parser | `packages/kit/src/routes/config/getStateFromPath.ts` | | Extension path parser | `packages/kit/src/routes/config/getStateFromPath.ext.ts` | | Deep link config | `packages/kit/src/routes/config/deeplink/index.ts` | | Deep link consts | `packages/shared/src/consts/deeplinkConsts.tsx` | | QR handlers | `packages/kit-bg/src/services/ServiceScanQRCode/utils/parseQRCode/handlers/` | | Page component | `packages/components/src/layouts/Page/index.tsx` | | Page lifecycle | `packages/components/src/layouts/Page/hooks.ts` |