From 7dee5152cfc8cbb83f8bd610c9536985ebadbdbd Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Thu, 21 May 2026 09:17:26 +0200 Subject: [PATCH 1/9] [expo-observe] don't record TTI two times (#46023) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why `markInteractive` could record the `expo.navigation.tti` metric more than once per screen — when the user navigates away and back to the same screen (A → B → A). # How 1. Gate on `interactiveScreensIds` directly — if the screen ID has already been marked interactive, return early before any TTI work. 2. Remove the now-unused `lastInteractiveCall` field from `ScreenTimes` and the bookkeeping around it in `useObserveForRouter`. # Test Plan 1. CI 2. Two new cases in `useObserveForRouter.test.native.tsx`: - `markInteractive` called twice without a new navigation records the metric once - re-focusing the screen after A → B → A does not record a second metric 3. Observe tester # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- .../integrations/expo-router/storage.d.ts | 1 - .../integrations/expo-router/storage.d.ts.map | 2 +- .../expo-router/useObserveForRouter.d.ts.map | 2 +- .../useObserveForRouter.test.native.tsx | 45 +++++++++++++++++++ .../src/integrations/expo-router/storage.ts | 1 - .../expo-router/useObserveForRouter.ts | 23 ++-------- 6 files changed, 51 insertions(+), 23 deletions(-) diff --git a/packages/expo-observe/build/integrations/expo-router/storage.d.ts b/packages/expo-observe/build/integrations/expo-router/storage.d.ts index 6beb2565038d99..244553b1eae468 100644 --- a/packages/expo-observe/build/integrations/expo-router/storage.d.ts +++ b/packages/expo-observe/build/integrations/expo-router/storage.d.ts @@ -1,7 +1,6 @@ import type { ActionDispatchedEvent } from 'expo-router'; export interface ScreenTimes { dispatchTime: number; - lastInteractiveCall?: number; } export interface PendingAction { actionType: ActionDispatchedEvent['actionType']; diff --git a/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map b/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map index 718b9a4d2056dc..2c1cae5e33a1c3 100644 --- a/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map +++ b/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACzC,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAQzE"} \ No newline at end of file +{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACzC,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAQzE"} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map index 3cf60807177096..42c1b457ca59b3 100644 --- a/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map +++ b/packages/expo-observe/build/integrations/expo-router/useObserveForRouter.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"useObserveForRouter.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAqC,MAAM,kBAAkB,CAAC;AASrE,KAAK,eAAe,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAE9D,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,IAAI,CA2G5D"} \ No newline at end of file +{"version":3,"file":"useObserveForRouter.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/useObserveForRouter.ts"],"names":[],"mappings":"AAAA,OAAO,UAAqC,MAAM,kBAAkB,CAAC;AASrE,KAAK,eAAe,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAE9D,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,IAAI,CA4F5D"} \ No newline at end of file diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx index ad7d3dfbf2b830..828186a693f762 100644 --- a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx @@ -152,6 +152,51 @@ describe('useObserveForRouter', () => { }); }); + it('records the navigation TTI metric only once when markInteractive is called twice without a new navigation', async () => { + storage.screenTimes['screen-a'] = { dispatchTime: 1000, isAppLaunch: false }; + const nowSpy = jest.spyOn(performance, 'now'); + + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + + nowSpy.mockReturnValue(1300); + await act(async () => { + await result.current!(); + }); + nowSpy.mockReturnValue(1500); + await act(async () => { + await result.current!(); + }); + + expect(mockAddCustomMetric).toHaveBeenCalledTimes(1); + }); + + it('does not record TTI metric when the screen is re-focused after navigating away (A → B → A)', async () => { + storage.screenTimes['screen-a'] = { dispatchTime: 1000, isAppLaunch: false }; + const nowSpy = jest.spyOn(performance, 'now'); + + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + + nowSpy.mockReturnValue(1300); + await act(async () => { + await result.current!(); + }); + + // Simulate navigating away to B and back to A: the focus listener would + // overwrite dispatchTime on screen-a with the new action's dispatch time. + storage.screenTimes['screen-a'] = { + ...storage.screenTimes['screen-a'], + dispatchTime: 2000, + }; + + nowSpy.mockReturnValue(2300); + await act(async () => { + await result.current!(); + }); + + expect(mockAddCustomMetric).toHaveBeenCalledTimes(1); + expect(mockAddCustomMetric).toHaveBeenNthCalledWith(1, expect.objectContaining({ value: 0.3 })); + }); + it('does not call AppMetrics.markInteractive when the screen is not focused, but still computes TTI', async () => { mockUseNavigation.mockReturnValue({ isFocused: () => false }); storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; diff --git a/packages/expo-observe/src/integrations/expo-router/storage.ts b/packages/expo-observe/src/integrations/expo-router/storage.ts index 0a4656efd09e95..c51cd86b5dc07f 100644 --- a/packages/expo-observe/src/integrations/expo-router/storage.ts +++ b/packages/expo-observe/src/integrations/expo-router/storage.ts @@ -2,7 +2,6 @@ import type { ActionDispatchedEvent } from 'expo-router'; export interface ScreenTimes { dispatchTime: number; - lastInteractiveCall?: number; } export interface PendingAction { diff --git a/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts b/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts index 542c5152701206..1ed0b1f92106c8 100644 --- a/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts +++ b/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts @@ -72,29 +72,14 @@ export function useObserveForRouter(): MarkInteractive | null { ); } - // Snapshot times BEFORE writing the new interactive timestamp so the - // duplicate-detection logic below sees the *previous* call, not this one. - const currentScreenData = storage.screenTimes[screenId]; - + // TTI is recorded once per screen ID for the lifetime of the storage — + // re-focusing the same screen (A → B → A) must not produce a second metric + if (storage.interactiveScreensIds.has(screenId)) return; storage.interactiveScreensIds.add(screenId); - if (storage.screenTimes[screenId]) { - storage.screenTimes[screenId] = { - ...storage.screenTimes[screenId], - lastInteractiveCall: now, - }; - } + const currentScreenData = storage.screenTimes[screenId]; if (!currentScreenData?.dispatchTime) return; - const previousInteractiveCall = currentScreenData.lastInteractiveCall; - const previousWasAfterDispatch = - previousInteractiveCall != null && currentScreenData.dispatchTime < previousInteractiveCall; - - if (previousWasAfterDispatch) { - // We only want to record interactive once per navigation - return; - } - // Stored in seconds to match the OTel `unit = "s"` convention const interactiveTimeSeconds = (now - currentScreenData.dispatchTime) / 1000; const mainSessionId = (await AppMetrics.getMainSession())?.id; From 509a9d604eb33ed140b370a9f8a3205197855512 Mon Sep 17 00:00:00 2001 From: duguyihou Date: Thu, 21 May 2026 17:31:51 +1000 Subject: [PATCH 2/9] docs(expo-router): update CLAUDE.md to reflect vendored react-navigation (#46061) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Removes the outdated "Built on top of React Navigation" description — `@react-navigation/*` packages are no longer external dependencies - Notes that React Navigation source is vendored into `src/react-navigation/` - Adds the full `src/react-navigation/` directory tree to the Structure section - Clarifies that `fork/` contains Expo-specific overrides on top of the vendored code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 --- packages/expo-router/CLAUDE.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/expo-router/CLAUDE.md b/packages/expo-router/CLAUDE.md index 09ac6f564b1f63..f7bdff71095573 100644 --- a/packages/expo-router/CLAUDE.md +++ b/packages/expo-router/CLAUDE.md @@ -1,6 +1,6 @@ # Expo Router -File-based routing library for React Native and web applications. Built on top of React Navigation, it provides automatic route generation from file structure, deep linking, typed routes, and cross-platform navigation. +File-based routing library for React Native and web applications. It provides automatic route generation from file structure, deep linking, typed routes, and cross-platform navigation. React Navigation's core code is vendored/forked into `src/react-navigation/` — there are no external `@react-navigation/*` dependencies. ## Structure @@ -71,7 +71,18 @@ File-based routing library for React Native and web applications. Built on top o │ │ ├── Sitemap.tsx # Route introspection │ │ └── Unmatched.tsx # 404 screen │ │ -│ ├── fork/ # Forked React Navigation code +│ ├── react-navigation/ # Vendored React Navigation source (replaces all @react-navigation/* deps) +│ │ ├── core/ # @react-navigation/core internals +│ │ ├── native/ # @react-navigation/native (linking, back-button) +│ │ ├── native-stack/ # @react-navigation/native-stack +│ │ ├── bottom-tabs/ # @react-navigation/bottom-tabs +│ │ ├── stack/ # @react-navigation/stack +│ │ ├── drawer/ # @react-navigation/drawer +│ │ ├── material-top-tabs/ # @react-navigation/material-top-tabs +│ │ ├── elements/ # @react-navigation/elements +│ │ └── routers/ # @react-navigation/routers +│ │ +│ ├── fork/ # Expo-specific overrides on top of the vendored react-navigation code │ │ ├── NavigationContainer.tsx # Custom NavigationContainer │ │ ├── getStateFromPath.ts # URL → navigation state │ │ ├── getPathFromState.ts # Navigation state → URL From c6fe815122d8f06337d370777b38e8e9ab7c0465 Mon Sep 17 00:00:00 2001 From: Alan Hughes <30924086+alanjhughes@users.noreply.github.com> Date: Thu, 21 May 2026 08:43:13 +0100 Subject: [PATCH 3/9] Use consistent `react-native-safe-area-context` version (#46068) --- apps/bare-expo/ios/Podfile.lock | 16 +-- apps/bare-expo/package.json | 2 +- apps/expo-go/ios/Podfile.lock | 2 +- apps/native-component-list/package.json | 2 +- apps/native-tests/ios/Podfile.lock | 124 ++++++++++++------------ apps/notification-tester/package.json | 2 +- apps/observe-tester/package.json | 2 +- apps/router-e2e/package.json | 2 +- apps/sandbox/package.json | 2 +- pnpm-lock.yaml | 111 +++++++++++++++------ 10 files changed, 159 insertions(+), 106 deletions(-) diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index aca636888da9a1..49e669c633cd6f 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -2317,7 +2317,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context (5.6.2): + - react-native-safe-area-context (5.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2329,8 +2329,8 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-safe-area-context/common (= 5.6.2) - - react-native-safe-area-context/fabric (= 5.6.2) + - react-native-safe-area-context/common (= 5.7.0) + - react-native-safe-area-context/fabric (= 5.7.0) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -2341,7 +2341,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/common (5.6.2): + - react-native-safe-area-context/common (5.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2363,7 +2363,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-safe-area-context/fabric (5.6.2): + - react-native-safe-area-context/fabric (5.7.0): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3437,7 +3437,7 @@ DEPENDENCIES: - "react-native-keyboard-controller (from `../../../node_modules/.pnpm/react-native-keyboard-controller@1.21.6_react-native-reanimated@4.3.1_patch_hash=1e34e4_e9da8032dc92e07ee8e9f9de5f82aa13/node_modules/react-native-keyboard-controller`)" - "react-native-netinfo (from `../../../node_modules/.pnpm/@react-native-community+netinfo@11.5.2_patch_hash=ced0cb79848978ecc3e780a4d812d94868434_57cdd99b2abbd81bf66bfe06e3966bfe/node_modules/@react-native-community/netinfo`)" - "react-native-pager-view (from `../../../node_modules/.pnpm/react-native-pager-view@6.9.1_react-native@0.85.3_@babel+core@7.29.0_@react-native+jest_635cad3fc277b440fe37e814de1bc4f1/node_modules/react-native-pager-view`)" - - "react-native-safe-area-context (from `../../../node_modules/.pnpm/react-native-safe-area-context@5.6.2_react-native@0.85.3_@babel+core@7.29.0_@react-nati_b5c9982a06f140c157b72454b7505cf1/node_modules/react-native-safe-area-context`)" + - "react-native-safe-area-context (from `../../../node_modules/.pnpm/react-native-safe-area-context@5.7.0_react-native@0.85.3_@babel+core@7.29.0_@react-nati_5218ce1da1bc499456ea2ce83b894307/node_modules/react-native-safe-area-context`)" - "react-native-segmented-control (from `../../../node_modules/.pnpm/@react-native-segmented-control+segmented-control@2.5.7_react-native@0.85.3_@babel+core_d06c98e80c699e170f8b37806ce468ec/node_modules/@react-native-segmented-control/segmented-control`)" - "react-native-skia (from `../../../node_modules/.pnpm/@shopify+react-native-skia@2.4.18_react-native-reanimated@4.3.1_patch_hash=1e34e4238541_c7ed8dcc454df7c13ebf24d246f99025/node_modules/@shopify/react-native-skia`)" - "react-native-slider (from `../../../node_modules/.pnpm/@react-native-community+slider@5.1.2/node_modules/@react-native-community/slider`)" @@ -3851,7 +3851,7 @@ EXTERNAL SOURCES: react-native-pager-view: :path: "../../../node_modules/.pnpm/react-native-pager-view@6.9.1_react-native@0.85.3_@babel+core@7.29.0_@react-native+jest_635cad3fc277b440fe37e814de1bc4f1/node_modules/react-native-pager-view" react-native-safe-area-context: - :path: "../../../node_modules/.pnpm/react-native-safe-area-context@5.6.2_react-native@0.85.3_@babel+core@7.29.0_@react-nati_b5c9982a06f140c157b72454b7505cf1/node_modules/react-native-safe-area-context" + :path: "../../../node_modules/.pnpm/react-native-safe-area-context@5.7.0_react-native@0.85.3_@babel+core@7.29.0_@react-nati_5218ce1da1bc499456ea2ce83b894307/node_modules/react-native-safe-area-context" react-native-segmented-control: :path: "../../../node_modules/.pnpm/@react-native-segmented-control+segmented-control@2.5.7_react-native@0.85.3_@babel+core_d06c98e80c699e170f8b37806ce468ec/node_modules/@react-native-segmented-control/segmented-control" react-native-skia: @@ -4098,7 +4098,7 @@ SPEC CHECKSUMS: react-native-keyboard-controller: 91fb57a926597b9d16c8438bc88f1142a169715c react-native-netinfo: 4319d381f35bc11b53dafebd5cd99c04c66a9fb4 react-native-pager-view: a3cb9627d41fa2f31ad2b312af6e3f10f831f26d - react-native-safe-area-context: 91a90d98c310adcc90a511e5aeb6046d7c19d885 + react-native-safe-area-context: 6b4966397ada0f7dd481e4486a2ef936834861a1 react-native-segmented-control: bf6e0032726727498e18dd437ae88afcdbc18e99 react-native-skia: 93613a9428b7f33530e5e085e95a89f023548320 react-native-slider: f9910f69950b9953c46e091377c1265b71eb01f0 diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index 38abd3b136cea2..da5e09ac2ce103 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -88,7 +88,7 @@ "react-native-keyboard-controller": "^1.20.7", "react-native-pager-view": "6.9.1", "react-native-reanimated": "4.3.1", - "react-native-safe-area-context": "5.6.2", + "react-native-safe-area-context": "5.7.0", "react-native-screens": "4.25.1", "react-native-svg": "15.15.4", "react-native-view-shot": "4.0.3", diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock index 089340f7f97b5a..f5d4ff366e5813 100644 --- a/apps/expo-go/ios/Podfile.lock +++ b/apps/expo-go/ios/Podfile.lock @@ -4723,7 +4723,7 @@ SPEC CHECKSUMS: GoogleAppMeasurement: 8a82b93a6400c8e6551c0bcd66a9177f2e067aed GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - hermes-engine: 971c315ad976a4dcbf09795cb72e85ea3f7200d9 + hermes-engine: 020d7dbf64ea2ac7f2dc43408d80cc6035c4b3e8 libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index 05d595bd9ae0f4..723e40a8bb7c54 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -153,7 +153,7 @@ "react-native-pager-view": "8.0.0", "react-native-paper": "^5.12.5", "react-native-reanimated": "4.3.1", - "react-native-safe-area-context": "5.6.2", + "react-native-safe-area-context": "5.7.0", "react-native-screens": "4.25.1", "react-native-svg": "15.15.4", "react-native-view-shot": "4.0.3", diff --git a/apps/native-tests/ios/Podfile.lock b/apps/native-tests/ios/Podfile.lock index 6738af70785add..c348155c4f6e92 100644 --- a/apps/native-tests/ios/Podfile.lock +++ b/apps/native-tests/ios/Podfile.lock @@ -6,12 +6,12 @@ PODS: - ExpoModulesTestCore - EXJSONUtils (56.0.0) - EXJSONUtils/Tests (56.0.0) - - EXManifests (56.0.3): + - EXManifests (56.0.4): - ExpoModulesCore - - EXManifests/Tests (56.0.3): + - EXManifests/Tests (56.0.4): - ExpoModulesCore - ExpoModulesTestCore - - Expo (56.0.0-preview.10): + - Expo (56.0.0): - ExpoModulesCore - ExpoModulesJSI - hermes-engine @@ -37,9 +37,9 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-launcher (56.0.9): + - expo-dev-launcher (56.0.13): - EXManifests - - expo-dev-launcher/Main (= 56.0.9) + - expo-dev-launcher/Main (= 56.0.13) - expo-dev-menu - expo-dev-menu-interface - ExpoModulesCore @@ -68,7 +68,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-launcher/Main (56.0.9): + - expo-dev-launcher/Main (56.0.13): - EXManifests - expo-dev-launcher/Unsafe - expo-dev-menu @@ -99,7 +99,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-launcher/Tests (56.0.9): + - expo-dev-launcher/Tests (56.0.13): - EXManifests - expo-dev-menu - expo-dev-menu-interface @@ -134,7 +134,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-launcher/Unsafe (56.0.9): + - expo-dev-launcher/Unsafe (56.0.13): - EXManifests - expo-dev-menu - expo-dev-menu-interface @@ -164,8 +164,8 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-menu (56.0.8): - - expo-dev-menu/Main (= 56.0.8) + - expo-dev-menu (56.0.12): + - expo-dev-menu/Main (= 56.0.12) - hermes-engine - RCTRequired - RCTTypeSafety @@ -188,7 +188,7 @@ PODS: - ReactNativeDependencies - Yoga - expo-dev-menu-interface (56.0.1) - - expo-dev-menu/Main (56.0.8): + - expo-dev-menu/Main (56.0.12): - EXManifests - expo-dev-menu-interface - ExpoModulesCore @@ -214,7 +214,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-menu/Tests (56.0.8): + - expo-dev-menu/Tests (56.0.12): - ExpoModulesTestCore - hermes-engine - Nimble @@ -240,7 +240,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-menu/UITests (56.0.8): + - expo-dev-menu/UITests (56.0.12): - ExpoModulesTestCore - hermes-engine - RCTRequired @@ -266,7 +266,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - Expo/Tests (56.0.0-preview.10): + - Expo/Tests (56.0.0): - ExpoModulesCore - ExpoModulesJSI - ExpoModulesTestCore @@ -293,7 +293,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoAppMetrics (56.0.7): + - ExpoAppMetrics (56.0.11): - ExpoModulesCore - EXUpdatesInterface - hermes-engine @@ -317,7 +317,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoAppMetrics/Tests (56.0.7): + - ExpoAppMetrics/Tests (56.0.11): - ExpoModulesCore - EXUpdatesInterface - hermes-engine @@ -341,10 +341,10 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoBackgroundTask (56.0.7): + - ExpoBackgroundTask (56.0.11): - ExpoModulesCore - ExpoTaskManager - - ExpoBackgroundTask/Tests (56.0.7): + - ExpoBackgroundTask/Tests (56.0.11): - ExpoModulesCore - ExpoModulesTestCore - ExpoTaskManager @@ -353,14 +353,14 @@ PODS: - ExpoClipboard/Tests (56.0.3): - ExpoModulesCore - ExpoModulesTestCore - - ExpoImage (56.0.4): + - ExpoImage (56.0.6): - ExpoModulesCore - libavif/libdav1d - SDWebImage (~> 5.21.0) - SDWebImageAVIFCoder (~> 0.11.0) - SDWebImageSVGCoder (~> 1.7.0) - SDWebImageWebPCoder (~> 0.14.6) - - ExpoImage/Tests (56.0.4): + - ExpoImage/Tests (56.0.6): - ExpoModulesCore - ExpoModulesTestCore - libavif/libdav1d @@ -368,14 +368,14 @@ PODS: - SDWebImageAVIFCoder (~> 0.11.0) - SDWebImageSVGCoder (~> 1.7.0) - SDWebImageWebPCoder (~> 0.14.6) - - ExpoMediaLibrary (56.0.4): + - ExpoMediaLibrary (56.0.5): - ExpoModulesCore - React-Core - - ExpoMediaLibrary/Tests (56.0.4): + - ExpoMediaLibrary/Tests (56.0.5): - ExpoModulesCore - ExpoModulesTestCore - React-Core - - ExpoModulesCore (56.0.7): + - ExpoModulesCore (56.0.11): - ExpoModulesJSI - hermes-engine - RCTRequired @@ -399,7 +399,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoModulesCore/Tests (56.0.7): + - ExpoModulesCore/Tests (56.0.11): - ExpoModulesJSI - ExpoModulesTestCore - hermes-engine @@ -424,23 +424,23 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoModulesJSI (56.0.3): + - ExpoModulesJSI (56.0.7): - React-Core - ReactCommon - - ExpoModulesJSI/Tests (56.0.3): + - ExpoModulesJSI/Tests (56.0.7): - React-Core - ReactCommon - - ExpoModulesTestCore (56.0.1): + - ExpoModulesTestCore (56.0.4): - ExpoModulesCore - Nimble (~> 13.0.0) - Quick (~> 7.3.0) - React-hermes - - ExpoNotifications (56.0.7): + - ExpoNotifications (56.0.11): - ExpoModulesCore - - ExpoNotifications/Tests (56.0.7): + - ExpoNotifications/Tests (56.0.11): - ExpoModulesCore - ExpoModulesTestCore - - ExpoObserve (56.0.7): + - ExpoObserve (56.0.11): - EASClient - ExpoAppMetrics - ExpoModulesCore @@ -465,7 +465,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoObserve/Tests (56.0.7): + - ExpoObserve/Tests (56.0.11): - EASClient - ExpoAppMetrics - ExpoModulesCore @@ -490,23 +490,23 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoRouter (56.1.4): + - ExpoRouter (56.2.3): - ExpoModulesCore - RNScreens - - ExpoRouter/Tests (56.1.4): + - ExpoRouter/Tests (56.2.3): - ExpoModulesCore - ExpoModulesTestCore - RNScreens - - ExpoTaskManager (56.0.7): + - ExpoTaskManager (56.0.11): - ExpoModulesCore - UMAppLoader - - ExpoTaskManager/Tests (56.0.7): + - ExpoTaskManager/Tests (56.0.11): - ExpoModulesCore - ExpoModulesTestCore - UMAppLoader - EXStructuredHeaders (56.0.0) - EXStructuredHeaders/Tests (56.0.0) - - EXUpdates (56.0.10): + - EXUpdates (56.0.14): - EASClient - EXManifests - ExpoModulesCore @@ -534,7 +534,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - EXUpdates/Tests (56.0.10): + - EXUpdates/Tests (56.0.14): - EASClient - EXManifests - ExpoModulesCore @@ -2576,7 +2576,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - RNScreens (4.25.0): + - RNScreens (4.25.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2598,9 +2598,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNScreens/common (= 4.25.0) + - RNScreens/common (= 4.25.1) - Yoga - - RNScreens/common (4.25.0): + - RNScreens/common (4.25.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2834,7 +2834,7 @@ DEPENDENCIES: - "RNCMaskedView (from `../../../node_modules/.pnpm/@react-native-masked-view+masked-view@0.3.2_react-native@0.85.3_@babel+core@7.29.0_@rea_da87b02ae003a7f77089f8e22aa53d01/node_modules/@react-native-masked-view/masked-view`)" - "RNGestureHandler (from `../../../node_modules/.pnpm/react-native-gesture-handler@2.30.0_react-native@0.85.3_@babel+core@7.29.0_@react-nativ_9a6358180c81a5519b6da847dcb246ed/node_modules/react-native-gesture-handler`)" - "RNReanimated (from `../../../node_modules/.pnpm/react-native-reanimated@4.3.1_patch_hash=1e34e4238541638db96b94d5a2e974e73f3b801788a3d8_1a5e3d09dfc1ab41347a1ceaceaf61bc/node_modules/react-native-reanimated`)" - - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.25.0_react-native@0.85.3_@babel+core@7.29.0_@react-native+jest-p_27bafc37770a4b472b96129a8e818eae/node_modules/react-native-screens`)" + - "RNScreens (from `../../../node_modules/.pnpm/react-native-screens@4.25.1_react-native@0.85.3_@babel+core@7.29.0_@react-native+jest-p_0d10e5dd559afc8ece7b3afa426cb96c/node_modules/react-native-screens`)" - "RNWorklets (from `../../../node_modules/.pnpm/react-native-worklets@0.8.3_patch_hash=3f49a21b44ba558989a3366eeff9c92ee331e18b736dbe89_42b4111e02dad0db2c0db5ed881424fc/node_modules/react-native-worklets`)" - UMAppLoader (from `../../../packages/unimodules-app-loader/ios`) - UMAppLoader/Tests (from `../../../packages/unimodules-app-loader/ios`) @@ -3074,7 +3074,7 @@ EXTERNAL SOURCES: RNReanimated: :path: "../../../node_modules/.pnpm/react-native-reanimated@4.3.1_patch_hash=1e34e4238541638db96b94d5a2e974e73f3b801788a3d8_1a5e3d09dfc1ab41347a1ceaceaf61bc/node_modules/react-native-reanimated" RNScreens: - :path: "../../../node_modules/.pnpm/react-native-screens@4.25.0_react-native@0.85.3_@babel+core@7.29.0_@react-native+jest-p_27bafc37770a4b472b96129a8e818eae/node_modules/react-native-screens" + :path: "../../../node_modules/.pnpm/react-native-screens@4.25.1_react-native@0.85.3_@babel+core@7.29.0_@react-native+jest-p_0d10e5dd559afc8ece7b3afa426cb96c/node_modules/react-native-screens" RNWorklets: :path: "../../../node_modules/.pnpm/react-native-worklets@0.8.3_patch_hash=3f49a21b44ba558989a3366eeff9c92ee331e18b736dbe89_42b4111e02dad0db2c0db5ed881424fc/node_modules/react-native-worklets" UMAppLoader: @@ -3086,28 +3086,28 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: EASClient: 2321f8d99fa86c710a6f68e017f3b54366baed9f EXJSONUtils: dba2755f4e24009eaf87a876b2d615ea06c16e42 - EXManifests: 67869a6d8efa7cb53a7b8b53f451d378c124073f - Expo: 44aecef3556a88407ec4aaa4f1d672088c7d85ef - expo-dev-launcher: 0dac6c478b1325d827f015d72ebd1c7705e57413 - expo-dev-menu: 422a6a7188c134d0b7e72748e7b622133e0fe916 + EXManifests: 7e19be8df665a338493060a419847dae76ffcd43 + Expo: bbddb135ce862854a8351782c4ac2923d5ca4898 + expo-dev-launcher: 2aec76e53c72eb67e0d0e4a4495337293cd13af9 + expo-dev-menu: 533fd236470d22a1d16bb810fc6823171723a9e1 expo-dev-menu-interface: 65402d4affb8b418aa6cec29b3abb0e313c8f443 - ExpoAppMetrics: c8a40f0baf474493a1e732e7ffb78b7750169084 - ExpoBackgroundTask: be387114533be898d4f9ed5120eb1288183578ce + ExpoAppMetrics: 886c20aadd776a9af1530fef0b8f70411c4e2be0 + ExpoBackgroundTask: efebaec6454fdc8bbd6117240be2c381d817d798 ExpoClipboard: eee843698341fd26d571fd2807800f98abb6123c - ExpoImage: 59f71ed6d030241ffadead114064cfd01bd0dc12 - ExpoMediaLibrary: 2ee3defd62fa8da9cb80fdcca71968cdeec7b24f - ExpoModulesCore: 575102f00470ad80a763c1a6b7ef952d668800ca - ExpoModulesJSI: f672bac18356446b024588f7a77b364d80909e71 - ExpoModulesTestCore: 8a57d88aaeae674725ab11d01c43c138d279dfc9 - ExpoNotifications: d4a747ad585d5d5c5101de08c252080e3fae820c - ExpoObserve: 7d9c3e168eacf6dbcd9a5a6ae677412a9a5e6ab1 - ExpoRouter: 8a4b4a2c4796ae20c87affc536e5d2089197ba3e - ExpoTaskManager: 19399a53bf382076051b07c9aed79a1191cd0f1e + ExpoImage: 08e5a5b1071b1e76793582f152e0e5eebf7269b9 + ExpoMediaLibrary: 136a8c3993f9178f537879dd7aded4b263ca9e53 + ExpoModulesCore: 605a046a7bd62fbc568c716281c4b74c7e98cc16 + ExpoModulesJSI: f25a013ea9a79904bdd535e4bea0872e155cde09 + ExpoModulesTestCore: 63adc8f54c0db5122aca43f7d09c45b4573089fc + ExpoNotifications: a360386d2944c24e5ce4543d8a5b13d01f994527 + ExpoObserve: f3bf7aa608758070f6128ac737d775a7ae2227e5 + ExpoRouter: 4ba271124971ccf2829ca20666e483006aeaaa44 + ExpoTaskManager: 05817b5a954f978ff7ae213a2708d72bb50adba3 EXStructuredHeaders: 9e89bcdd636ae2ecb59995cfba3230f5d7547c08 - EXUpdates: a736b2b35ca152b45b5abf0c2d45409a7deee697 + EXUpdates: 906f203e6f735c22457fa9b59a041598bdd49a72 EXUpdatesInterface: 25408a97d682355eb9fb37e5aa6e22caece1881f FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d - hermes-engine: 407f52f1dca2c25263c5f76e80cc38a39e0f6ec0 + hermes-engine: 725fd85144e1348879039099a6be950c471a4f2c libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 @@ -3123,7 +3123,7 @@ SPEC CHECKSUMS: React: e2dc35338068bbd299c66f043ae0d7f25de8499e React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48 React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0 - React-Core-prebuilt: cf0ea92a0ddc51494c68ab97610d31dc2c71a41e + React-Core-prebuilt: 0dd6fe0448efeb17392a241cc1ddd40457640a86 React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146 React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716 React-debug: 92944dc4d89f56d640e75498266cbde557a48189 @@ -3186,11 +3186,11 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2 ReactCodegen: 363b5e311c22e0e907a7a79b5d8fd095fbe4e561 ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f - ReactNativeDependencies: 5b4aaa18ba20efebc7a0a839b6bba354b1522eac + ReactNativeDependencies: 007472c1976e86bdfd1177d1dfeb6b3e0e5bd7b8 RNCMaskedView: eb2b2e538afa907f05a5848a1a1ac26092e6fec9 RNGestureHandler: c84901d120acdae2f6f27b5889a7cf144e64e6ec RNReanimated: a1b89be9f4b3f85c708900d0a167cd22d869a198 - RNScreens: a6a509f05ea74d50abab8c21b319b33cfca1ea1c + RNScreens: 75d04ea58bd7a8e8ac60b7968d0382aa71631a7d RNWorklets: edcd0af162eba9fb81af89a4761f1af35086d1cc SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 diff --git a/apps/notification-tester/package.json b/apps/notification-tester/package.json index adc28cd4d147b5..0d79ed7d3fb8f4 100644 --- a/apps/notification-tester/package.json +++ b/apps/notification-tester/package.json @@ -42,7 +42,7 @@ "react": "19.2.3", "react-native": "0.85.3", "react-native-reanimated": "4.3.0", - "react-native-safe-area-context": "5.6.2", + "react-native-safe-area-context": "5.7.0", "react-native-screens": "4.25.1", "react-native-worklets": "0.8.3" }, diff --git a/apps/observe-tester/package.json b/apps/observe-tester/package.json index 34279a593e2193..6f4785a2d7cb2e 100644 --- a/apps/observe-tester/package.json +++ b/apps/observe-tester/package.json @@ -43,7 +43,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "react-native": "0.85.3", - "react-native-safe-area-context": "5.6.2", + "react-native-safe-area-context": "5.7.0", "react-native-screens": "4.25.1" }, "devDependencies": { diff --git a/apps/router-e2e/package.json b/apps/router-e2e/package.json index 49c76a17634a9b..ad2ce391c94677 100644 --- a/apps/router-e2e/package.json +++ b/apps/router-e2e/package.json @@ -78,7 +78,7 @@ "react-dom": "19.2.3", "react-native": "0.85.3", "react-native-gesture-handler": "~2.30.0", - "react-native-safe-area-context": "5.6.2", + "react-native-safe-area-context": "5.7.0", "react-native-screens": "4.25.1", "react-native-web": "^0.21.0", "react-native-webview": "13.16.1" diff --git a/apps/sandbox/package.json b/apps/sandbox/package.json index e6c312b0631cb3..696529c60e0154 100644 --- a/apps/sandbox/package.json +++ b/apps/sandbox/package.json @@ -17,7 +17,7 @@ "expo-splash-screen": "workspace:*", "react": "19.2.3", "react-native": "0.85.3", - "react-native-safe-area-context": "5.6.2", + "react-native-safe-area-context": "5.7.0", "react-native-screens": "~4.25.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef0f442d86cbe4..1b283c0d1664b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,13 +120,13 @@ importers: version: 2.5.7(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/bottom-tabs': specifier: ^7.15.5 - version: 7.15.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.15.5(aa9215ff53128ba023ce32c04c484301) '@react-navigation/native': specifier: ^7.1.33 version: 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': specifier: ^7.14.5 - version: 7.14.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.14.5(aa9215ff53128ba023ce32c04c484301) '@shopify/flash-list': specifier: 2.0.2 version: 2.0.2(@babel/runtime@7.29.2)(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -230,8 +230,8 @@ importers: specifier: 4.3.1 version: 4.3.1(patch_hash=1e34e4238541638db96b94d5a2e974e73f3b801788a3d8f5c3f4b237a0559138)(react-native-worklets@0.8.3(patch_hash=3f49a21b44ba558989a3366eeff9c92ee331e18b736dbe89c5962ecc6f2802f1)(@babel/core@7.29.0)(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: - specifier: 5.6.2 - version: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + specifier: 5.7.0 + version: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: 4.25.1 version: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -817,22 +817,22 @@ importers: version: 2.5.7(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/bottom-tabs': specifier: ^7.15.5 - version: 7.15.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.15.5(aa9215ff53128ba023ce32c04c484301) '@react-navigation/drawer': specifier: ^7.9.4 - version: 7.9.4(4ef6282f53919f189908d641d5b06cf6) + version: 7.9.4(04d71eb4df642158655d9d16b5b24cbe) '@react-navigation/elements': specifier: ^2.9.10 - version: 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + version: 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/native': specifier: ^7.1.33 version: 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': specifier: ^7.14.5 - version: 7.14.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.14.5(aa9215ff53128ba023ce32c04c484301) '@react-navigation/stack': specifier: ^7.8.5 - version: 7.8.5(22573f9cca2ca72f53fa15df55f013ee) + version: 7.8.5(bd6f9e954608f04b0c30c2c8af25b736) '@shopify/flash-list': specifier: 2.0.2 version: 2.0.2(@babel/runtime@7.29.2)(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -1123,13 +1123,13 @@ importers: version: 8.0.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-paper: specifier: ^5.12.5 - version: 5.15.0(react-native-safe-area-context@5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + version: 5.15.0(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-reanimated: specifier: 4.3.1 version: 4.3.1(patch_hash=1e34e4238541638db96b94d5a2e974e73f3b801788a3d8f5c3f4b237a0559138)(react-native-worklets@0.8.3(patch_hash=3f49a21b44ba558989a3366eeff9c92ee331e18b736dbe89c5962ecc6f2802f1)(@babel/core@7.29.0)(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: - specifier: 5.6.2 - version: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + specifier: 5.7.0 + version: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: 4.25.1 version: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -1257,7 +1257,7 @@ importers: version: 15.1.1(expo-font@packages+expo-font)(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/bottom-tabs': specifier: ^7.15.5 - version: 7.15.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.15.5(aa9215ff53128ba023ce32c04c484301) '@react-navigation/native': specifier: ^7.1.33 version: 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -1316,8 +1316,8 @@ importers: specifier: 4.3.0 version: 4.3.0(patch_hash=1e34e4238541638db96b94d5a2e974e73f3b801788a3d8f5c3f4b237a0559138)(react-native-worklets@0.8.3(patch_hash=3f49a21b44ba558989a3366eeff9c92ee331e18b736dbe89c5962ecc6f2802f1)(@babel/core@7.29.0)(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: - specifier: 5.6.2 - version: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + specifier: 5.7.0 + version: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: 4.25.1 version: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -1351,13 +1351,13 @@ importers: version: 15.1.1(expo-font@packages+expo-font)(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/bottom-tabs': specifier: ^7.15.5 - version: 7.15.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.15.5(aa9215ff53128ba023ce32c04c484301) '@react-navigation/native': specifier: ^7.1.33 version: 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/native-stack': specifier: ^7.14.5 - version: 7.14.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.14.5(aa9215ff53128ba023ce32c04c484301) expo: specifier: workspace:* version: link:../../packages/expo @@ -1425,8 +1425,8 @@ importers: specifier: 0.85.3 version: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) react-native-safe-area-context: - specifier: 5.6.2 - version: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + specifier: 5.7.0 + version: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: 4.25.1 version: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -1495,8 +1495,8 @@ importers: specifier: ~2.30.0 version: 2.30.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-safe-area-context: - specifier: 5.6.2 - version: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + specifier: 5.7.0 + version: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: 4.25.1 version: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -1539,7 +1539,7 @@ importers: dependencies: '@react-navigation/bottom-tabs': specifier: ^7.15.5 - version: 7.15.5(eca7daf14a0c27a5d8d11de74da1c346) + version: 7.15.5(aa9215ff53128ba023ce32c04c484301) '@react-navigation/native': specifier: ^7.1.33 version: 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -1565,8 +1565,8 @@ importers: specifier: 0.85.3 version: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) react-native-safe-area-context: - specifier: 5.6.2 - version: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + specifier: 5.7.0 + version: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: specifier: ~4.25.1 version: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -18952,6 +18952,19 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@react-navigation/bottom-tabs@7.15.5(aa9215ff53128ba023ce32c04c484301)': + dependencies: + '@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + color: 4.2.3 + react: 19.2.3 + react-native: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + sf-symbols-typescript: 2.2.0 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + '@react-navigation/bottom-tabs@7.15.5(eca7daf14a0c27a5d8d11de74da1c346)': dependencies: '@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -18977,9 +18990,9 @@ snapshots: use-latest-callback: 0.2.6(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@react-navigation/drawer@7.9.4(4ef6282f53919f189908d641d5b06cf6)': + '@react-navigation/drawer@7.9.4(04d71eb4df642158655d9d16b5b24cbe)': dependencies: - '@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) '@react-navigation/native': 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) color: 4.2.3 react: 19.2.3 @@ -18987,7 +19000,7 @@ snapshots: react-native-drawer-layout: 4.2.2(7ddf79089c35e29e6117a946c4322c39) react-native-gesture-handler: 2.30.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-reanimated: 4.3.1(patch_hash=1e34e4238541638db96b94d5a2e974e73f3b801788a3d8f5c3f4b237a0559138)(react-native-worklets@0.8.3(patch_hash=3f49a21b44ba558989a3366eeff9c92ee331e18b736dbe89c5962ecc6f2802f1)(@babel/core@7.29.0)(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) - react-native-safe-area-context: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) react-native-screens: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) transitivePeerDependencies: @@ -19005,6 +19018,32 @@ snapshots: optionalDependencies: '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@react-navigation/elements@2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': + dependencies: + '@react-navigation/native': 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + color: 4.2.3 + react: 19.2.3 + react-native: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + use-latest-callback: 0.2.6(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + optionalDependencies: + '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + + '@react-navigation/native-stack@7.14.5(aa9215ff53128ba023ce32c04c484301)': + dependencies: + '@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + color: 4.2.3 + react: 19.2.3 + react-native: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + sf-symbols-typescript: 2.2.0 + warn-once: 0.1.1 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + '@react-navigation/native-stack@7.14.5(eca7daf14a0c27a5d8d11de74da1c346)': dependencies: '@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) @@ -19047,6 +19086,20 @@ snapshots: transitivePeerDependencies: - '@react-native-masked-view/masked-view' + '@react-navigation/stack@7.8.5(bd6f9e954608f04b0c30c2c8af25b736)': + dependencies: + '@react-navigation/elements': 2.9.10(@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(@react-navigation/native@7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@react-navigation/native': 7.1.33(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + color: 4.2.3 + react: 19.2.3 + react-native: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-native-gesture-handler: 2.30.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.25.1(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + use-latest-callback: 0.2.6(react@19.2.3) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + '@rtsao/scc@1.1.0': {} '@segment/loosely-validate-event@2.0.0': @@ -25222,13 +25275,13 @@ snapshots: react: 19.2.3 react-native: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) - react-native-paper@5.15.0(react-native-safe-area-context@5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): + react-native-paper@5.15.0(react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): dependencies: '@callstack/react-theme-provider': 3.0.9(react@19.2.3) color: 3.2.1 react: 19.2.3 react-native: 0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) - react-native-safe-area-context: 5.6.2(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) use-latest-callback: 0.2.6(react@19.2.3) react-native-reanimated@4.3.0(patch_hash=1e34e4238541638db96b94d5a2e974e73f3b801788a3d8f5c3f4b237a0559138)(react-native-worklets@0.8.3(patch_hash=3f49a21b44ba558989a3366eeff9c92ee331e18b736dbe89c5962ecc6f2802f1)(@babel/core@7.29.0)(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.0)(@react-native/jest-preset@0.85.3(@babel/core@7.29.0)(react@19.2.3))(@react-native/metro-config@0.85.3(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): From 9011383ce8a3a9fe8b94a91cae66361beed214d0 Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Thu, 21 May 2026 09:53:15 +0200 Subject: [PATCH 4/9] [expo-observe] fix navigation tti on app start (#46022) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why `expo.navigation.tti` was silently skipped for the very first screen on app launch — the initial focus handler recorded `cold_ttr` but never seeded `dispatchTime`, so `useObserveForRouter` had nothing to diff against on `markInteractive`. The tti metric also had no way to distinguish app-launch navigations from in-app ones. # How 1. In `initListeners`, on the initial app-launch focus, seed `storage.screenTimes[screenId]` with `dispatchTime: appLaunchTime` and `isAppLaunch: true` 2. On subsequent navigated focuses, set `isAppLaunch: false` on the seeded screen entry. 3. Add `isAppLaunch?: boolean` to the `ScreenTimes` storage shape. 4. In `useObserveForRouter`, forward `isAppLaunch` into the `tti` metric `params`. # Test Plan 1. CI 2. `init.test.native.ts`, `useObserveForRouter.test.native.tsx` 3. Observe tester # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .../integrations/expo-router/init.d.ts.map | 2 +- .../integrations/expo-router/storage.d.ts | 1 + .../integrations/expo-router/storage.d.ts.map | 2 +- .../expo-router/__tests__/init.test.native.ts | 27 +++++++++++++++++ .../useObserveForRouter.test.native.tsx | 30 +++++++++++++++---- .../src/integrations/expo-router/init.ts | 9 ++++++ .../src/integrations/expo-router/storage.ts | 1 + .../expo-router/useObserveForRouter.ts | 2 +- 8 files changed, 65 insertions(+), 9 deletions(-) diff --git a/packages/expo-observe/build/integrations/expo-router/init.d.ts.map b/packages/expo-observe/build/integrations/expo-router/init.d.ts.map index 65e369939a8aac..09eb1600a4c2a0 100644 --- a/packages/expo-observe/build/integrations/expo-router/init.d.ts.map +++ b/packages/expo-observe/build/integrations/expo-router/init.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/init.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAS1D,eAAO,MAAM,aAAa,eAAoB,CAAC;AAE/C,wBAAgB,qBAAqB,SAGpC;AAED,KAAK,gBAAgB,GAAG,WAAW,CAAC,OAAO,cAAc,CAAC,CAAC,2BAA2B,CAAC,CAAC;AAExF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,wBAAwB,EACjC,gBAAgB,EAAE,gBAAgB,GACjC,MAAM,IAAI,CAkFZ"} \ No newline at end of file +{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/init.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAS1D,eAAO,MAAM,aAAa,eAAoB,CAAC;AAE/C,wBAAgB,qBAAqB,SAGpC;AAED,KAAK,gBAAgB,GAAG,WAAW,CAAC,OAAO,cAAc,CAAC,CAAC,2BAA2B,CAAC,CAAC;AAExF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,wBAAwB,EACjC,gBAAgB,EAAE,gBAAgB,GACjC,MAAM,IAAI,CA2FZ"} \ No newline at end of file diff --git a/packages/expo-observe/build/integrations/expo-router/storage.d.ts b/packages/expo-observe/build/integrations/expo-router/storage.d.ts index 244553b1eae468..c04d6ef8956fa8 100644 --- a/packages/expo-observe/build/integrations/expo-router/storage.d.ts +++ b/packages/expo-observe/build/integrations/expo-router/storage.d.ts @@ -1,6 +1,7 @@ import type { ActionDispatchedEvent } from 'expo-router'; export interface ScreenTimes { dispatchTime: number; + isAppLaunch?: boolean; } export interface PendingAction { actionType: ActionDispatchedEvent['actionType']; diff --git a/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map b/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map index 2c1cae5e33a1c3..47c80455be2854 100644 --- a/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map +++ b/packages/expo-observe/build/integrations/expo-router/storage.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACzC,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAQzE"} \ No newline at end of file +{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/integrations/expo-router/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,qBAAqB,EAAE,OAAO,CAAC;IAC/B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACzC,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAQzE"} \ No newline at end of file diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.native.ts b/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.native.ts index dcb84601ce0a69..4a4eb883de9ed0 100644 --- a/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.native.ts +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/init.test.native.ts @@ -127,6 +127,33 @@ describe('initListeners', () => { }); }); + it('seeds dispatchTime and isAppLaunch=true for the initial screen so a later markInteractive can compute navigation TTI', async () => { + focus(events, 'a'); + await flushAsync(); + + // The initial focus is treated as if the app launch dispatched the + // navigation — without this, useObserveForRouter has no dispatchTime to + // diff against and the navigation `tti` metric is silently skipped. + expect(storage.screenTimes['a']).toEqual({ + dispatchTime: expect.any(Number), + isAppLaunch: true, + }); + }); + + it('seeds isAppLaunch=false on subsequent navigated focuses so markInteractive can label the tti metric', async () => { + focus(events, 'a'); + await flushAsync(); + + dispatch(events, 'NAVIGATE'); + focus(events, 'b'); + await flushAsync(); + + expect(storage.screenTimes['b']).toEqual({ + dispatchTime: expect.any(Number), + isAppLaunch: false, + }); + }); + it('records cold_ttr with isAppLaunch=false on subsequent focuses of a new screen', async () => { dispatch(events, 'NAVIGATE'); focus(events, 'a'); diff --git a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx index 828186a693f762..3668302138cb6d 100644 --- a/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx +++ b/packages/expo-observe/src/integrations/expo-router/__tests__/useObserveForRouter.test.native.tsx @@ -79,7 +79,7 @@ beforeEach(() => { describe('useObserveForRouter', () => { it('records TTI from dispatchTime on the first call when focused', async () => { - storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; + storage.screenTimes['screen-a'] = { dispatchTime: 1000, isAppLaunch: false }; jest.spyOn(performance, 'now').mockReturnValue(1300); const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); @@ -95,7 +95,7 @@ describe('useObserveForRouter', () => { routeName: '/test', name: 'tti', value: 0.3, - params: { routeParams: { x: '1' }, url: '/test' }, + params: { isAppLaunch: false, routeParams: { x: '1' }, url: '/test' }, }); }); @@ -134,13 +134,31 @@ describe('useObserveForRouter', () => { routeName: expectedRouteName, name: 'tti', value: 0.3, - params: { routeParams, url: pathname }, + params: { isAppLaunch: false, routeParams, url: pathname }, }); } ); + it('records TTI with isAppLaunch=true when the initial screen was seeded by app launch', async () => { + storage.screenTimes['screen-a'] = { dispatchTime: 1000, isAppLaunch: true }; + jest.spyOn(performance, 'now').mockReturnValue(1300); + + const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); + await act(async () => { + await result.current!(); + }); + + expect(mockAddCustomMetric).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'tti', + value: 0.3, + params: { isAppLaunch: true, routeParams: { x: '1' }, url: '/test' }, + }) + ); + }); + it('calls AppMetrics.markInteractive when the screen is focused', async () => { - storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; + storage.screenTimes['screen-a'] = { dispatchTime: 1000, isAppLaunch: false }; const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); const arg = { params: { x: 'payload' } }; await act(async () => { @@ -199,7 +217,7 @@ describe('useObserveForRouter', () => { it('does not call AppMetrics.markInteractive when the screen is not focused, but still computes TTI', async () => { mockUseNavigation.mockReturnValue({ isFocused: () => false }); - storage.screenTimes['screen-a'] = { dispatchTime: 1000 }; + storage.screenTimes['screen-a'] = { dispatchTime: 1000, isAppLaunch: false }; jest.spyOn(performance, 'now').mockReturnValue(1300); const { result } = renderHook(() => useObserveForRouter(), { wrapper: wrapper(storage) }); @@ -215,7 +233,7 @@ describe('useObserveForRouter', () => { routeName: '/test', name: 'tti', value: 0.3, - params: { routeParams: { x: '1' }, url: '/test' }, + params: { isAppLaunch: false, routeParams: { x: '1' }, url: '/test' }, }); }); diff --git a/packages/expo-observe/src/integrations/expo-router/init.ts b/packages/expo-observe/src/integrations/expo-router/init.ts index ab67a6af3f5074..0f11743ee73a7f 100644 --- a/packages/expo-observe/src/integrations/expo-router/init.ts +++ b/packages/expo-observe/src/integrations/expo-router/init.ts @@ -65,6 +65,14 @@ export function initListeners( // Stored in seconds to match the OTel `unit = "s"` convention const appLaunchTtrSeconds = (now - appLaunchTime) / 1000; storage.hasRecordedInitialTtr = true; + // Seed dispatchTime so a later markInteractive on the initial screen can + // diff against app launch — symmetric with the navigated-focus branch + // below, which seeds dispatchTime from the last pending action. + storage.screenTimes[e.screenId] = { + ...storage.screenTimes[e.screenId], + dispatchTime: appLaunchTime, + isAppLaunch: true, + }; AppMetrics.addCustomMetricToSession({ sessionId: mainSessionId, timestamp, @@ -85,6 +93,7 @@ export function initListeners( storage.screenTimes[e.screenId] = { ...storage.screenTimes[e.screenId], dispatchTime, + isAppLaunch: false, }; AppMetrics.addCustomMetricToSession({ diff --git a/packages/expo-observe/src/integrations/expo-router/storage.ts b/packages/expo-observe/src/integrations/expo-router/storage.ts index c51cd86b5dc07f..7758035a02793b 100644 --- a/packages/expo-observe/src/integrations/expo-router/storage.ts +++ b/packages/expo-observe/src/integrations/expo-router/storage.ts @@ -2,6 +2,7 @@ import type { ActionDispatchedEvent } from 'expo-router'; export interface ScreenTimes { dispatchTime: number; + isAppLaunch?: boolean; } export interface PendingAction { diff --git a/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts b/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts index 1ed0b1f92106c8..a1508f3b50430e 100644 --- a/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts +++ b/packages/expo-observe/src/integrations/expo-router/useObserveForRouter.ts @@ -93,7 +93,7 @@ export function useObserveForRouter(): MarkInteractive | null { routeName: routePattern, name: 'tti', value: interactiveTimeSeconds, - params: { routeParams, url: pathname }, + params: { isAppLaunch: !!currentScreenData.isAppLaunch, routeParams, url: pathname }, }); } }, From 32b27a99c16188ffc808563dc0b98b8581b044b0 Mon Sep 17 00:00:00 2001 From: Christian Falch <875252+chrfalch@users.noreply.github.com> Date: Thu, 21 May 2026 10:06:04 +0200 Subject: [PATCH 5/9] fix(publish, precompile) include and consume shared xcframework deps in precompiled pods pipeline (#46069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why Previously creating npm packages was done without including the shared deps (e.x: ExpoImage and ExpoImageManipulator shared underlying dependencies). This caused runtime crashes with missing dylibs. ## How This fixes the problem by doing this: - NPM packages: Adding shared dependencies to packages (will duplicate in f.ex. expo-image/manipulator - but only one is used at link time) - Pod install: Added handling of the shared packages in pod install - Release/debug: Added replacing of shared deps as well in replace-xcframework.js (switching release/debug) ## Test-plan ✅ Run Release build of BareExpo, verify that it has the image manipulator and expo image shared deps (SDWebImage*) ## Checklist - [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [x] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). --------- Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com> --- .../expo-modules-autolinking/CHANGELOG.md | 2 + .../scripts/ios/precompiled_modules.rb | 220 +++++++++++++----- .../scripts/ios/replace-xcframework.js | 196 +++++++--------- .../tasks/bundleIOSPrebuilds.ts | 59 ++++- 4 files changed, 314 insertions(+), 163 deletions(-) diff --git a/packages/expo-modules-autolinking/CHANGELOG.md b/packages/expo-modules-autolinking/CHANGELOG.md index 763af95b486ee3..2ce694bd357457 100644 --- a/packages/expo-modules-autolinking/CHANGELOG.md +++ b/packages/expo-modules-autolinking/CHANGELOG.md @@ -6,6 +6,8 @@ ### 🎉 New features +- [iOS] Include and consume shared SPM dependencies in the precompiled pod / npm publish pipeline. ([#46069](https://github.com/expo/expo/pull/46069) by [@chrfalch](https://github.com/chrfalch)) + ### 🐛 Bug fixes ### 💡 Others diff --git a/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb b/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb index 4310e3c95af7ea..524b7d00a82a52 100644 --- a/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb +++ b/packages/expo-modules-autolinking/scripts/ios/precompiled_modules.rb @@ -54,6 +54,11 @@ module PrecompiledModules # Centralized build output directory under packages/precompile/ PRECOMPILE_BUILD_DIR = '.build'.freeze + # Subdir under packages/precompile/.build/ holding //.xcframework (monorepo source). + SHARED_SPM_DEPS_SOURCE_DIR = '.spm-deps'.freeze + # Subdir inside an npm package that bundles shared SPM xcframeworks for standalone consumers. + BUNDLED_SHARED_SPM_DEPS_SUBPATH = File.join('prebuilds', 'spm-deps').freeze + # Apple platforms supported by CocoaPods podspecs APPLE_PLATFORMS = %w[ios osx tvos watchos visionos].freeze @@ -188,10 +193,14 @@ def perform_post_install(installer) # - Pods that vendor xcframeworks (already precompiled) # - Source-built pods that depend on React-Core (non-modular includes) # + # Also stages shared SPM dep xcframework symlinks inside their owner pod's + # directory — must run before `generate_pods_project` reads each xcframework's + # Info.plist to slice it. + # # @param installer [Pod::Installer] The CocoaPods installer instance def perform_pre_install(installer) return unless enabled? - return unless prebuilt_react_active? + ensure_shared_spm_deps(installer) return if linkage(installer).nil? pods_to_downgrade = Set.new(installer.podfile.framework_modules_to_patch) @@ -215,6 +224,63 @@ def t.build_type end end + # Symlinks each shared SPM dependency xcframework (e.g. SDWebImage) into the + # pod directory of its owner. Ownership is whatever `build_vendored_paths` set + # in `@framework_owner_map` during `store_podspec` (resolution-first); + # falls back to alphabetical-first only if the map has no entry. Must run + # before `generate_pods_project` so CocoaPods sees the symlinks when reading + # each xcframework's Info.plist. + def ensure_shared_spm_deps(installer) + return unless enabled? + + consumers_by_dep = collect_shared_spm_deps(installer) + return if consumers_by_dep.empty? + + @framework_owner_map ||= {} + @claimed_vendored_frameworks ||= Set.new + + unresolved = [] + staged = 0 + + consumers_by_dep.each do |dep_name, consumers| + existing = @framework_owner_map[dep_name] + owner_name = (existing && consumers.key?(existing)) ? existing : consumers.keys.sort.first + owner_info = consumers[owner_name] + @framework_owner_map[dep_name] ||= owner_name + @claimed_vendored_frameworks.add(dep_name) + + source_path = shared_spm_dep_xcframework_path(dep_name, owner_info, build_flavor) + owner_pod_dir = File.join(installer.sandbox.root, owner_name) + unless source_path && File.directory?(owner_pod_dir) + unresolved << dep_name + next + end + + FileUtils.rm_rf(File.join(owner_pod_dir, "#{dep_name}.xcframework")) + File.symlink(source_path, File.join(owner_pod_dir, "#{dep_name}.xcframework")) + staged += 1 + end + + if unresolved.any? + Pod::UI.warn "[Expo-precompiled] Shared SPM xcframeworks not found for: #{unresolved.join(', ')} (flavor: #{build_flavor}). The Expo modules that depend on them will fail at runtime with dyld 'Library not loaded: @rpath/.framework/'. Run the precompile prebuild pipeline, or ensure each consuming npm package ships prebuilds/spm-deps///.xcframework." + end + + Pod::UI.puts "[Expo] ".blue + "Staged #{staged}/#{consumers_by_dep.size} shared SPM xcframework(s) (#{build_flavor})" if staged > 0 + end + + # dep_name => { pod_name => pod_info } for shared SPM deps consumed by enabled prebuilt pods in this install. + def collect_shared_spm_deps(installer) + by_dep = {} + installer.pod_targets.each do |pod_target| + info = pod_lookup_map[pod_target.name] + next unless info && has_prebuilt_xcframework?(pod_target.name) + (info[:spm_dependency_frameworks] || []).each do |dep_name| + (by_dep[dep_name] ||= {})[pod_target.name] = info + end + end + by_dep + end + # ────────────────────────────────────────────────────────────────────── # Cache management # ────────────────────────────────────────────────────────────────────── @@ -1108,15 +1174,10 @@ def strip_matching_dependencies(deps_hash, bundled, pod_name) end end - # Builds the vendored_frameworks paths array for a prebuilt pod. - # Deduplicates shared SPM dependency frameworks across multiple prebuilt pods: - # the first pod to claim a framework "owns" it; subsequent pods skip it and - # instead get FRAMEWORK_SEARCH_PATHS pointing at the owning pod's directory. - # - # @param product_name [String] The product/module name - # @param pod_info [Hash] Package info from spm.config.json lookup - # @param pod_name [String] The pod name (for summary tracking) - # @return [Array] vendored framework paths + # Returns vendored_frameworks paths for a prebuilt pod: the product's own + # xcframework plus any shared SPM deps this pod owns (first to claim wins; + # non-owners get FRAMEWORK_SEARCH_PATHS instead). Shared dep entries are + # symlinks staged by `ensure_shared_spm_deps` inside the owner's pod dir. def build_vendored_paths(product_name, pod_info, pod_name) @claimed_vendored_frameworks ||= Set.new @framework_owner_map ||= {} @@ -1126,38 +1187,27 @@ def build_vendored_paths(product_name, pod_info, pod_name) @framework_owner_map[product_name] = pod_name (pod_info[:spm_dependency_frameworks] || []).each do |dep_name| - if @claimed_vendored_frameworks.include?(dep_name) - owner = @framework_owner_map[dep_name] - Pod::UI.puts "#{'[Expo-precompiled] '.blue}Skipping #{dep_name}.xcframework from #{pod_name} — already vendored by #{owner}" - else + owner = (@framework_owner_map[dep_name] ||= pod_name) + if owner == pod_name paths << "#{dep_name}.xcframework" - @claimed_vendored_frameworks.add(dep_name) - @framework_owner_map[dep_name] = pod_name + else + Pod::UI.puts "#{'[Expo-precompiled] '.blue}Skipping #{dep_name}.xcframework from #{pod_name} — already vendored by #{owner}" end log_spm_dependency(pod_name, dep_name) end paths end - # Returns FRAMEWORK_SEARCH_PATHS entries for shared SPM dependency frameworks - # that were claimed by another prebuilt pod. The non-owning pod needs these - # paths so the linker can find the xcframeworks at build time. - # - # @param pod_name [String] The pod name - # @param pod_info [Hash] Package info from spm.config.json lookup - # @return [Array] framework search path entries + # FRAMEWORK_SEARCH_PATHS entries for shared SPM deps claimed by another pod. + # CocoaPods slices each xcframework into `${PODS_XCFRAMEWORKS_BUILD_DIR}//`, + # which is where the linker resolves the framework. def framework_search_paths_for_skipped_deps(pod_name, pod_info) - @claimed_vendored_frameworks ||= Set.new @framework_owner_map ||= {} - - paths = [] - (pod_info[:spm_dependency_frameworks] || []).each do |dep_name| + owners = (pod_info[:spm_dependency_frameworks] || []).filter_map do |dep_name| owner = @framework_owner_map[dep_name] - if owner && owner != pod_name - paths << "\"${PODS_ROOT}/#{owner}\"" - end - end - paths.uniq + owner if owner && owner != pod_name + end.uniq + owners.flat_map { |owner| [%("${PODS_XCFRAMEWORKS_BUILD_DIR}/#{owner}"), %("${PODS_ROOT}/#{owner}")] } end # ────────────────────────────────────────────────────────────────────── @@ -1184,11 +1234,13 @@ def build_script_phases_json(spec_name, product_name, pod_info) package_root_var = "#{pods_parent}/#{package_root_rel}" dsym_stamp = "$(DERIVED_FILE_DIR)/expo-dsym-resolve-#{product_name}-$(CONFIGURATION).stamp" + shared_deps = shared_dep_switch_args(spec_name, pod_info) + switch_phase = { 'name' => "[Expo] Switch #{spec_name} XCFramework for build configuration", 'execution_position' => 'before_compile', 'input_files' => ["#{pods_parent}/#{switch_script_rel}"], - 'script' => xcframework_switch_script(product_name, xcframeworks_dir_var, switch_script_path), + 'script' => xcframework_switch_script(product_name, xcframeworks_dir_var, switch_script_path, shared_deps), } if Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.13.0') @@ -1231,32 +1283,55 @@ def prepare_command_script(product_name, build_output_dir) SH end - # Returns the shell script for the xcframework switch phase. - def xcframework_switch_script(product_name, xcframeworks_dir, script_path) - <<~SH - # Switch between debug/release XCFramework based on build configuration - # This script is auto-generated by expo-modules-autolinking - + # Shell script for the xcframework switch phase. With no shared deps the + # script short-circuits in shell when the per-pod state file matches; with + # shared deps Node is always invoked so each dep's symlink can be repointed + # (it has its own per-dep state file inside replace-xcframework.js). + def xcframework_switch_script(product_name, xcframeworks_dir, script_path, shared_deps = []) + config_detect = <<~SH.chomp CONFIG="release" if echo "$GCC_PREPROCESSOR_DEFINITIONS" | grep -q "DEBUG=1"; then CONFIG="debug" fi - - # Early exit: Skip Node.js invocation if configuration hasn't changed - # This optimization avoids ~100-200ms overhead per module on incremental builds - LAST_CONFIG_FILE="#{xcframeworks_dir}/artifacts/.last_build_configuration" - if [ -f "$LAST_CONFIG_FILE" ] && [ "$(cat "$LAST_CONFIG_FILE")" = "$CONFIG" ]; then - exit 0 - fi - - # Configuration changed or first build - invoke Node.js to extract tarball - . "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh" - - "$NODE_BINARY" "#{script_path}" \\ - -c "$CONFIG" \\ - -m "#{product_name}" \\ - -x "#{xcframeworks_dir}" SH + + if shared_deps.empty? + <<~SH + # Auto-generated by expo-modules-autolinking + #{config_detect} + LAST_CONFIG_FILE="#{xcframeworks_dir}/artifacts/.last_build_configuration" + if [ -f "$LAST_CONFIG_FILE" ] && [ "$(cat "$LAST_CONFIG_FILE")" = "$CONFIG" ]; then + exit 0 + fi + . "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh" + "$NODE_BINARY" "#{script_path}" -c "$CONFIG" -m "#{product_name}" -x "#{xcframeworks_dir}" + SH + else + shared_args = shared_deps.map { |arg| " #{arg}" }.join(" \\\n") + <<~SH + # Auto-generated by expo-modules-autolinking + #{config_detect} + . "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh" + "$NODE_BINARY" "#{script_path}" \\ + -c "$CONFIG" \\ + -m "#{product_name}" \\ + -x "#{xcframeworks_dir}" \\ + #{shared_args} + SH + end + end + + # '--shared ":"' tokens for shared SPM deps this pod owns. + # Non-owners reach the framework via FRAMEWORK_SEARCH_PATHS at link time. + def shared_dep_switch_args(pod_name, pod_info) + return [] unless pod_info && pod_info[:spm_dependency_frameworks] + @framework_owner_map ||= {} + pod_info[:spm_dependency_frameworks].filter_map do |dep_name| + next nil unless @framework_owner_map[dep_name] == pod_name + source_base = shared_spm_dep_source_base(dep_name, pod_info) + next nil unless source_base + %(--shared "#{dep_name}:#{source_base.gsub(/[\\"$`]/) { |c| "\\#{c}" }}") + end end # Returns the shell script for the dSYM source map resolution phase. @@ -1853,6 +1928,43 @@ def _resolve_prebuilt_status_uncached(pod_name, visiting) own_resolution end + # Candidate parent dirs (each holds /.xcframework subtrees) for + # a shared SPM dep. Ordered: EXPO_PRECOMPILED_MODULES_PATH override, monorepo + # .spm-deps, then the consumer-side npm-bundled location. + def shared_spm_dep_source_base_candidates(dep_name, pod_info) + candidates = [] + candidates << File.join(custom_modules_path, SHARED_SPM_DEPS_SOURCE_DIR, dep_name) if custom_modules_path + candidates << File.join(memoized_repo_root, 'packages', 'precompile', PRECOMPILE_BUILD_DIR, SHARED_SPM_DEPS_SOURCE_DIR, dep_name) if memoized_repo_root + candidates << File.join(pod_info[:package_root], BUNDLED_SHARED_SPM_DEPS_SUBPATH, dep_name) if pod_info && pod_info[:package_root] + candidates + end + + # First candidate base that has at least one flavor on disk (used to build switch-script source_base args). + def shared_spm_dep_source_base(dep_name, pod_info) + shared_spm_dep_source_base_candidates(dep_name, pod_info).find do |base| + %w[debug release].any? { |f| File.directory?(File.join(base, f, "#{dep_name}.xcframework")) } + end + end + + # First candidate that has the requested flavor on disk (walks all candidates so a partial monorepo doesn't shadow a complete npm bundle). + def shared_spm_dep_xcframework_path(dep_name, pod_info, flavor) + shared_spm_dep_source_base_candidates(dep_name, pod_info).each do |base| + path = File.join(base, flavor, "#{dep_name}.xcframework") + return path if File.directory?(path) + end + nil + end + + def memoized_repo_root + return @repo_root if @memoized_repo_root_set + begin + @repo_root = find_repo_root + ensure + @memoized_repo_root_set = true + end + @repo_root + end + def resolve_prebuilt_tarball(pod_info, product_name, flavor, pod_name = nil) tarball = File.join(pod_info[:build_output_dir], flavor, 'xcframeworks', "#{product_name}.tar.gz") return tarball if File.exist?(tarball) diff --git a/packages/expo-modules-autolinking/scripts/ios/replace-xcframework.js b/packages/expo-modules-autolinking/scripts/ios/replace-xcframework.js index 11da29115ae368..c097e2f47845f4 100644 --- a/packages/expo-modules-autolinking/scripts/ios/replace-xcframework.js +++ b/packages/expo-modules-autolinking/scripts/ios/replace-xcframework.js @@ -2,52 +2,32 @@ /** * Replace XCFramework for Debug/Release Configuration * - * This script extracts the correct flavor tarball to switch between debug and release - * xcframeworks. It's invoked from a CocoaPods script_phase before each compile - * to ensure the correct XCFramework variant is linked. + * Per-pod product swap: extracts /artifacts/-.tar.gz + * over .xcframework, gated on /artifacts/.last_build_configuration. + * Sibling shared-dep symlinks under / are preserved (only the product + * xcframework is wiped before re-extracting). * - * Directory structure: - * / - * artifacts/ - * -debug.tar.gz (tarball, source of truth) - * -release.tar.gz (tarball, source of truth) - * .last_build_configuration - * .xcframework/ (real dir, extracted from tarball) - * .xcframework/ (real dir, if any, extracted from same tarball) + * Shared-dep repoint (each --shared entry): atomically replaces + * /.xcframework with a symlink to //.xcframework + * and writes /artifacts/.last_config. The owner pod (decided at pod + * install time by ensure_shared_spm_deps) receives --shared args for each dep it owns. * * Usage: - * node replace-xcframework.js -c -m -x - * - * Arguments: - * -c, --config Build configuration: "debug" or "release" - * -m, --module Module/product name (used for tarball lookup and logging) - * -x, --xcframeworks Path to the pod directory (Pods//) - * - * The script: - * 1. Finds the tarball: /artifacts/-.tar.gz - * 2. Checks artifacts/.last_build_configuration — skips if unchanged - * 3. Removes all *.xcframework directories in xcframeworksDir - * 4. Extracts the tarball: tar -xzf ... -C - * 5. Writes the new config to artifacts/.last_build_configuration + * node replace-xcframework.js -c -m -x + * [--shared :]... * * Based on React Native's replace-rncore-version.js pattern. */ +const { spawnSync } = require('child_process'); const fs = require('fs'); const path = require('path'); -const { spawnSync } = require('child_process'); const LOG_PREFIX = '[Expo XCFramework]'; -// Parse command line arguments function parseArgs() { const args = process.argv.slice(2); - const result = { - config: null, - module: null, - xcframeworksDir: null, - }; - + const result = { config: null, module: null, xcframeworksDir: null, sharedDeps: [] }; for (let i = 0; i < args.length; i++) { switch (args[i]) { case '-c': @@ -62,116 +42,120 @@ function parseArgs() { case '--xcframeworks': result.xcframeworksDir = args[++i]; break; + case '-s': + case '--shared': { + const spec = args[++i] || ''; + const colon = spec.indexOf(':'); + if (colon === -1) { + console.error(`${LOG_PREFIX} Invalid --shared (expected ":"): ${spec}`); + process.exit(1); + } + result.sharedDeps.push({ name: spec.slice(0, colon), sourceBase: spec.slice(colon + 1) }); + break; + } } } - return result; } -function main() { - const args = parseArgs(); - - // Validate arguments - if (!args.config || !args.module || !args.xcframeworksDir) { - console.error( - 'Usage: replace-xcframework.js -c -m -x ' - ); - console.error(' -c, --config Build configuration: "debug" or "release"'); - console.error(' -m, --module Module/product name'); - console.error(' -x, --xcframeworks Path to the xcframeworks directory'); - process.exit(1); - } - - // Normalize config to lowercase - const configLower = args.config.toLowerCase(); - if (configLower !== 'debug' && configLower !== 'release') { - console.error( - `${LOG_PREFIX} Invalid configuration: ${args.config}. Must be "debug" or "release".` - ); - process.exit(1); +function readState(file) { + try { + return fs.existsSync(file) ? fs.readFileSync(file, 'utf8').trim() : null; + } catch { + return null; } +} - const xcframeworksDir = args.xcframeworksDir; - const moduleName = args.module; - - // Validate xcframeworksDir exists +function processPerPodSwap(args, configLower) { + const { xcframeworksDir, module: moduleName } = args; if (!fs.existsSync(xcframeworksDir) || !fs.statSync(xcframeworksDir).isDirectory()) { console.error(`${LOG_PREFIX} ${moduleName}: Directory not found: ${xcframeworksDir}`); process.exit(1); } - // Ensure artifacts directory exists const artifactsDir = path.join(xcframeworksDir, 'artifacts'); fs.mkdirSync(artifactsDir, { recursive: true }); - - // Find the tarball for the requested configuration (stored in artifacts/) const tarballPath = path.join(artifactsDir, `${moduleName}-${configLower}.tar.gz`); const lastConfigFile = path.join(artifactsDir, '.last_build_configuration'); - // Check if tarball exists if (!fs.existsSync(tarballPath)) { - console.error( - `${LOG_PREFIX} ${moduleName}: Tarball not found at ${tarballPath}, skipping.` - ); + console.error(`${LOG_PREFIX} ${moduleName}: Tarball not found at ${tarballPath}, skipping.`); return; } - // Read last build configuration - let lastConfig = null; - if (fs.existsSync(lastConfigFile)) { - try { - lastConfig = fs.readFileSync(lastConfigFile, 'utf8').trim(); - } catch (e) { - // Ignore read errors — will proceed with extraction - } - } - - // Check if configuration has changed + const lastConfig = readState(lastConfigFile); if (lastConfig === configLower) { console.log(`${LOG_PREFIX} ${moduleName}: Already extracted ${configLower}, skipping.`); return; } - // Remove all existing *.xcframework directories - const entries = fs.readdirSync(xcframeworksDir); - for (const entry of entries) { - if (!entry.endsWith('.xcframework')) continue; - const entryPath = path.join(xcframeworksDir, entry); - - try { - const stat = fs.lstatSync(entryPath); - if (stat.isDirectory() || stat.isSymbolicLink()) { - fs.rmSync(entryPath, { recursive: true, force: true }); - } - } catch (e) { - console.error(`${LOG_PREFIX} ${moduleName}: Warning: failed to remove ${entry}: ${e.message}`); + // Only remove the product xcframework — shared-dep symlinks staged by + // ensure_shared_spm_deps are repointed separately below via --shared. + const productXcfw = path.join(xcframeworksDir, `${moduleName}.xcframework`); + try { + fs.rmSync(productXcfw, { recursive: true, force: true }); + } catch (e) { + if (e.code !== 'ENOENT') { + console.error(`${LOG_PREFIX} ${moduleName}: failed to remove product xcframework: ${e.message}`); } } - // Extract the tarball using spawnSync to avoid shell interpretation of paths - const result = spawnSync('tar', ['-xzf', tarballPath, '-C', xcframeworksDir], { - stdio: 'pipe', - }); - + const result = spawnSync('tar', ['-xzf', tarballPath, '-C', xcframeworksDir], { stdio: 'pipe' }); if (result.status !== 0) { - const stderr = result.stderr ? result.stderr.toString().trim() : 'unknown error'; - console.error(`${LOG_PREFIX} ${moduleName}: Failed to extract tarball: ${stderr}`); + console.error(`${LOG_PREFIX} ${moduleName}: tar failed: ${result.stderr?.toString().trim()}`); process.exit(1); } - // Write last build configuration - try { - fs.writeFileSync(lastConfigFile, configLower); - } catch (e) { - console.error(`${LOG_PREFIX} ${moduleName}: Warning: failed to write config file: ${e.message}`); + fs.writeFileSync(lastConfigFile, configLower); + console.log( + lastConfig + ? `${LOG_PREFIX} ${moduleName}: Switched from ${lastConfig} to ${configLower}.` + : `${LOG_PREFIX} ${moduleName}: Extracted ${configLower} tarball.` + ); +} + +function repointSharedDep(xcframeworksDir, name, sourceBase, configLower) { + const artifactsDir = path.join(xcframeworksDir, 'artifacts'); + fs.mkdirSync(artifactsDir, { recursive: true }); + const stateFile = path.join(artifactsDir, `${name}.last_config`); + const linkPath = path.join(xcframeworksDir, `${name}.xcframework`); + + // Trust the state file only when the symlink is still present — an externally + // deleted symlink (e.g. clear_cocoapods_cache wiping the pod dir) must trigger + // a re-link even if the state file claims the right config. + if (readState(stateFile) === configLower && fs.existsSync(linkPath)) return; + + const target = path.join(sourceBase, configLower, `${name}.xcframework`); + if (!fs.existsSync(target)) { + console.error( + `${LOG_PREFIX} Shared dep ${name}: target not found at ${target}. Run the precompile prebuild pipeline for the ${configLower} flavor, or ensure prebuilds/spm-deps/${name}/${configLower}/${name}.xcframework ships with the consuming package.` + ); + process.exit(1); } - if (lastConfig && lastConfig !== configLower) { - console.log( - `${LOG_PREFIX} ${moduleName}: Switched from ${lastConfig} to ${configLower} (extracted tarball).` + fs.rmSync(linkPath, { recursive: true, force: true }); + fs.symlinkSync(target, linkPath); + fs.writeFileSync(stateFile, configLower); + console.log(`${LOG_PREFIX} Shared dep ${name}: repointed to ${configLower} (${target}).`); +} + +function main() { + const args = parseArgs(); + if (!args.config || !args.module || !args.xcframeworksDir) { + console.error( + 'Usage: replace-xcframework.js -c -m -x [--shared :]...' ); - } else { - console.log(`${LOG_PREFIX} ${moduleName}: Extracted ${configLower} tarball.`); + process.exit(1); + } + const configLower = args.config.toLowerCase(); + if (configLower !== 'debug' && configLower !== 'release') { + console.error(`${LOG_PREFIX} Invalid configuration: ${args.config}. Must be "debug" or "release".`); + process.exit(1); + } + + processPerPodSwap(args, configLower); + for (const dep of args.sharedDeps) { + repointSharedDep(args.xcframeworksDir, dep.name, dep.sourceBase, configLower); } } diff --git a/tools/src/publish-packages/tasks/bundleIOSPrebuilds.ts b/tools/src/publish-packages/tasks/bundleIOSPrebuilds.ts index 223a5957194abf..e2aa45ef66b84a 100644 --- a/tools/src/publish-packages/tasks/bundleIOSPrebuilds.ts +++ b/tools/src/publish-packages/tasks/bundleIOSPrebuilds.ts @@ -119,9 +119,15 @@ const IOS_PREBUILD_PACKAGES = [ const PRECOMPILE_BUILD_DIR = path.join(PACKAGES_DIR, 'precompile', '.build'); const FLAVORS = ['debug', 'release'] as const; +// Shared SPM deps source (monorepo) and per-package bundle destination (standalone). +// Resolver: precompiled_modules.rb's shared_spm_dep_source_base. +const SHARED_SPM_DEPS_SOURCE_DIR = '.spm-deps'; +const SHARED_SPM_DEPS_BUNDLE_SUBPATH = path.join('prebuilds', 'spm-deps'); + /** - * Builds iOS xcframeworks for selected packages and copies the output tarballs - * into each package's `prebuilds/output/` directory so they are included in `npm pack`. + * Builds iOS xcframeworks for selected packages and bundles them into each package's + * `prebuilds/` directory: per-product tarballs under `prebuilds/output/` and shared SPM + * dep xcframeworks under `prebuilds/spm-deps/`. Both are picked up by `npm pack`. */ export const bundleIOSPrebuilds = new Task( { @@ -156,7 +162,8 @@ export const bundleIOSPrebuilds = new Task( skipCompose: false, skipVerify: false, verbose: false, - bundleSharedDeps: true, + // Shared SPM deps ship separately at /prebuilds/spm-deps/ (see bundleSharedSpmDepsAsync). + bundleSharedDeps: false, }); if (result.exitCode !== 0) { @@ -217,6 +224,8 @@ export const bundleIOSPrebuilds = new Task( } } } + + await bundleSharedSpmDepsAsync(parcel.pkg.path); }, `Bundled iOS prebuilds into ${pkgName}` ); @@ -226,3 +235,47 @@ export const bundleIOSPrebuilds = new Task( } } ); + +// Copies each shared SPM xcframework this package declares (across all products in its +// spm.config.json) from .spm-deps/ into /prebuilds/spm-deps///. +// Non-shared SPM packages (not present in .spm-deps/) are silently skipped. +async function bundleSharedSpmDepsAsync(packagePath: string): Promise { + const configPath = path.join(packagePath, 'spm.config.json'); + if (!fs.existsSync(configPath)) return; + let config: any; + try { + config = JSON.parse(await fs.promises.readFile(configPath, 'utf8')); + } catch (e) { + logger.warn(` Failed to parse ${configPath}: ${(e as Error).message}`); + return; + } + const depNames = new Set(); + for (const product of config.products ?? []) { + for (const spm of product.spmPackages ?? []) { + if (typeof spm?.productName === 'string') depNames.add(spm.productName); + } + } + + for (const depName of depNames) { + for (const flavor of FLAVORS) { + const srcXcfw = path.join( + PRECOMPILE_BUILD_DIR, + SHARED_SPM_DEPS_SOURCE_DIR, + depName, + flavor, + `${depName}.xcframework` + ); + if (!fs.existsSync(srcXcfw)) continue; + const destXcfw = path.join( + packagePath, + SHARED_SPM_DEPS_BUNDLE_SUBPATH, + depName, + flavor, + `${depName}.xcframework` + ); + await fs.promises.rm(destXcfw, { recursive: true, force: true }); + await fs.promises.mkdir(path.dirname(destXcfw), { recursive: true }); + await fs.promises.cp(srcXcfw, destXcfw, { recursive: true }); + } + } +} From 380eeb5e0a12334d48a7917710f074b5a27877ef Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Thu, 21 May 2026 10:01:04 +0100 Subject: [PATCH 6/9] chore(expo-module-scripts): Add `publishConfig.executableFiles` to expo-module-scripts (#46074) --- packages/expo-module-scripts/CHANGELOG.md | 2 ++ packages/expo-module-scripts/package.json | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/expo-module-scripts/CHANGELOG.md b/packages/expo-module-scripts/CHANGELOG.md index c9ea38becf6929..2405c2baa8f967 100644 --- a/packages/expo-module-scripts/CHANGELOG.md +++ b/packages/expo-module-scripts/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Add missing `publishConfig.executableFiles` ([#46074](https://github.com/expo/expo/pull/46074) by [@kitten](https://github.com/kitten)) + ### 💡 Others ## 56.0.2 — 2026-05-06 diff --git a/packages/expo-module-scripts/package.json b/packages/expo-module-scripts/package.json index 5a0cde1a949408..e534555d88ec27 100644 --- a/packages/expo-module-scripts/package.json +++ b/packages/expo-module-scripts/package.json @@ -90,5 +90,22 @@ "devDependencies": { "@expo/metro": "~56.0.0", "@babel/core": "^7.26.0" + }, + "publishConfig": { + "executableFiles": [ + "./bin/expo-module-babel", + "./bin/expo-module-clean", + "./bin/expo-module-configure", + "./bin/expo-module-eslint", + "./bin/expo-module-jest", + "./bin/expo-module-lint", + "./bin/expo-module-prepare", + "./bin/expo-module-prepublishOnly", + "./bin/expo-module-readme", + "./bin/expo-module-test", + "./bin/expo-module-tsc", + "./bin/expo-module-typecheck", + "./bin/expo-module.js" + ] } } From 21335e5c121427f1d1d422ab80656b893c341baf Mon Sep 17 00:00:00 2001 From: Alan Hughes Date: Thu, 21 May 2026 10:26:31 +0100 Subject: [PATCH 7/9] Publish packages expo-observe@56.0.12 expo-modules-autolinking@56.0.10 expo-template-tabs@56.0.15 expo-template-default@56.0.15 expo-template-blank@56.0.15 expo-template-blank-typescript@56.0.15 expo-template-bare-minimum@56.0.15 @expo/prebuild-config@56.0.11 @expo/cli@56.1.8 expo@56.0.1 --- apps/bare-expo/ios/Podfile.lock | 14 +++++++------- apps/expo-go/ios/Podfile.lock | 6 +++--- packages/@expo/cli/CHANGELOG.md | 4 ++++ packages/@expo/cli/package.json | 4 ++-- packages/@expo/prebuild-config/CHANGELOG.md | 4 ++++ packages/@expo/prebuild-config/package.json | 4 ++-- packages/expo-module-template/$package.json | 2 +- packages/expo-modules-autolinking/CHANGELOG.md | 8 ++++++-- packages/expo-modules-autolinking/package.json | 2 +- packages/expo-observe/package.json | 2 +- packages/expo/CHANGELOG.md | 4 ++++ packages/expo/android/build.gradle | 4 ++-- packages/expo/bundledNativeModules.json | 2 +- packages/expo/package.json | 6 +++--- pnpm-lock.yaml | 8 ++++---- templates/expo-template-bare-minimum/package.json | 4 ++-- .../expo-template-blank-typescript/package.json | 4 ++-- templates/expo-template-blank/package.json | 4 ++-- templates/expo-template-default/package.json | 4 ++-- templates/expo-template-tabs/package.json | 4 ++-- 20 files changed, 55 insertions(+), 39 deletions(-) diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index 49e669c633cd6f..34707195192beb 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -38,7 +38,7 @@ PODS: - EXManifests/Tests (56.0.4): - ExpoModulesCore - ExpoModulesTestCore - - Expo (56.0.0): + - Expo (56.0.1): - ExpoModulesCore - ExpoModulesJSI - hermes-engine @@ -299,7 +299,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - Expo/Tests (56.0.0): + - Expo/Tests (56.0.1): - ExpoModulesCore - ExpoModulesJSI - ExpoModulesTestCore @@ -588,7 +588,7 @@ PODS: - ExpoNotifications/Tests (56.0.11): - ExpoModulesCore - ExpoModulesTestCore - - ExpoObserve (56.0.11): + - ExpoObserve (56.0.12): - EASClient - ExpoAppMetrics - ExpoModulesCore @@ -613,7 +613,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - ExpoObserve/Tests (56.0.11): + - ExpoObserve/Tests (56.0.12): - EASClient - ExpoAppMetrics - ExpoModulesCore @@ -3967,7 +3967,7 @@ SPEC CHECKSUMS: EXConstants: 13f6719b5c0d537be9c7b169d54445b063f95213 EXJSONUtils: dba2755f4e24009eaf87a876b2d615ea06c16e42 EXManifests: 7e19be8df665a338493060a419847dae76ffcd43 - Expo: bbddb135ce862854a8351782c4ac2923d5ca4898 + Expo: 0794b44ac16db51ced9ce6a9402f3cdc93aaedce expo-dev-client: e7bdc2fee0cedb048f283eafd8439a09b6028a8a expo-dev-launcher: 2aec76e53c72eb67e0d0e4a4495337293cd13af9 expo-dev-menu: 533fd236470d22a1d16bb810fc6823171723a9e1 @@ -3975,7 +3975,7 @@ SPEC CHECKSUMS: ExpoAgeRange: c652074c8c24bf52bd3201ecd017040c867ba58b ExpoAppIntegrity: 43cc62f24c533b960de07b5038568acedc3a567a ExpoAppleAuthentication: 47f6f6c6722a7facef29d15397635b785086bc5e - ExpoAppMetrics: 886c20aadd776a9af1530fef0b8f70411c4e2be0 + ExpoAppMetrics: a9aca0e3a26a8d1a57f7f47b9f109f96c1964d95 ExpoAsset: 051b7b3bf1edecb3d8f3643b0b9dc3e308d43cb4 ExpoAudio: ad5b6e07a7ff22b89e7683d8a12cf51a54f6b90b ExpoBackgroundFetch: 52cefdd114179fba877d76fdcef2aa694cfce0fe @@ -4023,7 +4023,7 @@ SPEC CHECKSUMS: ExpoModulesWorkletsAdapter: 4d065a4e45a12246e2a111fbd4727bb98c74e691 ExpoNetwork: c9651806f67c5fe93d221f9b4533c98ae6833bb9 ExpoNotifications: a360386d2944c24e5ce4543d8a5b13d01f994527 - ExpoObserve: f3bf7aa608758070f6128ac737d775a7ae2227e5 + ExpoObserve: 546d058d32eae5a794f3131fd7908a7af757a2a4 ExpoPrint: 6b5bcac4492908b7e28144c2e7265c1c5ee4ade0 ExpoScreenCapture: c388565b6244aec9d96c18941efbee8655b2fec2 ExpoScreenOrientation: 733d1dc1fa97f2716442d4fa2b0abe1457d9d2dd diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock index f5d4ff366e5813..fdb8bc1c1f4230 100644 --- a/apps/expo-go/ios/Podfile.lock +++ b/apps/expo-go/ios/Podfile.lock @@ -20,7 +20,7 @@ PODS: - EXManifests/Tests (56.0.4): - ExpoModulesCore - ExpoModulesTestCore - - Expo (56.0.0): + - Expo (56.0.1): - boost - DoubleConversion - ExpoModulesCore @@ -52,7 +52,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - Expo/Tests (56.0.0): + - Expo/Tests (56.0.1): - boost - DoubleConversion - ExpoModulesCore @@ -4643,7 +4643,7 @@ SPEC CHECKSUMS: EXConstants: 13f6719b5c0d537be9c7b169d54445b063f95213 EXJSONUtils: dba2755f4e24009eaf87a876b2d615ea06c16e42 EXManifests: 7e19be8df665a338493060a419847dae76ffcd43 - Expo: 58bbb883cc6f2fb7a270d038beffdb59adf7a339 + Expo: 1947f699c1f4db845a6d7bf63a791d8bc89241be ExpoAsset: 051b7b3bf1edecb3d8f3643b0b9dc3e308d43cb4 ExpoAudio: ad5b6e07a7ff22b89e7683d8a12cf51a54f6b90b ExpoBackgroundFetch: 52cefdd114179fba877d76fdcef2aa694cfce0fe diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index 4ec3c150909cfd..9ec97affb10190 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -10,6 +10,10 @@ ### 💡 Others +## 56.1.8 — 2026-05-21 + +_This version does not introduce any user-facing changes._ + ## 56.1.7 — 2026-05-20 ### 🎉 New features diff --git a/packages/@expo/cli/package.json b/packages/@expo/cli/package.json index d1161f64f15348..f22e07245a21f4 100644 --- a/packages/@expo/cli/package.json +++ b/packages/@expo/cli/package.json @@ -1,6 +1,6 @@ { "name": "@expo/cli", - "version": "56.1.7", + "version": "56.1.8", "description": "The Expo CLI", "main": "main.js", "bin": { @@ -64,7 +64,7 @@ "@expo/osascript": "workspace:^2.6.0", "@expo/package-manager": "workspace:^1.12.0", "@expo/plist": "workspace:^0.7.0", - "@expo/prebuild-config": "workspace:^56.0.10", + "@expo/prebuild-config": "workspace:^56.0.11", "@expo/require-utils": "workspace:^56.1.2", "@expo/router-server": "workspace:^56.0.10", "@expo/schema-utils": "workspace:^56.0.0", diff --git a/packages/@expo/prebuild-config/CHANGELOG.md b/packages/@expo/prebuild-config/CHANGELOG.md index 0daa05b79516bc..70ce43770e267d 100644 --- a/packages/@expo/prebuild-config/CHANGELOG.md +++ b/packages/@expo/prebuild-config/CHANGELOG.md @@ -10,6 +10,10 @@ ### 💡 Others +## 56.0.11 — 2026-05-21 + +_This version does not introduce any user-facing changes._ + ## 56.0.10 — 2026-05-20 ### 🐛 Bug fixes diff --git a/packages/@expo/prebuild-config/package.json b/packages/@expo/prebuild-config/package.json index c5f6a5c9674e66..c3733fa5f63cf1 100644 --- a/packages/@expo/prebuild-config/package.json +++ b/packages/@expo/prebuild-config/package.json @@ -1,6 +1,6 @@ { "name": "@expo/prebuild-config", - "version": "56.0.10", + "version": "56.0.11", "description": "Get the prebuild config", "main": "build/index.js", "scripts": { @@ -45,7 +45,7 @@ "@expo/image-utils": "workspace:^0.10.0", "@expo/json-file": "workspace:^10.2.0", "@react-native/normalize-colors": "0.85.3", - "expo-modules-autolinking": "workspace:~56.0.9", + "expo-modules-autolinking": "workspace:~56.0.10", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0" diff --git a/packages/expo-module-template/$package.json b/packages/expo-module-template/$package.json index 478ee10d99f66d..359a124a0dfe3e 100644 --- a/packages/expo-module-template/$package.json +++ b/packages/expo-module-template/$package.json @@ -34,7 +34,7 @@ "babel-preset-expo": "~55.0.8", "eslint": "~9.39.4", "eslint-config-universe": "^15.0.3", - "expo": "^56.0.0", + "expo": "^56.0.1", "jest": "^29.7.0", "jest-expo": "~55.0.9", "prettier": "^3.0.0", diff --git a/packages/expo-modules-autolinking/CHANGELOG.md b/packages/expo-modules-autolinking/CHANGELOG.md index 2ce694bd357457..5f408ed4aef504 100644 --- a/packages/expo-modules-autolinking/CHANGELOG.md +++ b/packages/expo-modules-autolinking/CHANGELOG.md @@ -6,12 +6,16 @@ ### 🎉 New features -- [iOS] Include and consume shared SPM dependencies in the precompiled pod / npm publish pipeline. ([#46069](https://github.com/expo/expo/pull/46069) by [@chrfalch](https://github.com/chrfalch)) - ### 🐛 Bug fixes ### 💡 Others +## 56.0.10 — 2026-05-21 + +### 🎉 New features + +- [iOS] Include and consume shared SPM dependencies in the precompiled pod / npm publish pipeline. ([#46069](https://github.com/expo/expo/pull/46069) by [@chrfalch](https://github.com/chrfalch)) + ## 56.0.9 — 2026-05-20 ### 🐛 Bug fixes diff --git a/packages/expo-modules-autolinking/package.json b/packages/expo-modules-autolinking/package.json index 51f376e9f5c6d6..5842490b263d65 100644 --- a/packages/expo-modules-autolinking/package.json +++ b/packages/expo-modules-autolinking/package.json @@ -1,6 +1,6 @@ { "name": "expo-modules-autolinking", - "version": "56.0.9", + "version": "56.0.10", "description": "Scripts that autolink Expo modules.", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/packages/expo-observe/package.json b/packages/expo-observe/package.json index 203bc7a1b604a1..61457ac05caae9 100644 --- a/packages/expo-observe/package.json +++ b/packages/expo-observe/package.json @@ -1,7 +1,7 @@ { "name": "expo-observe", "title": "Expo Observe", - "version": "56.0.11", + "version": "56.0.12", "description": "Expo module that dispatches collected app metrics to EAS Observe", "main": "src/index.ts", "types": "build/index.d.ts", diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index 67f202633ae6d1..e0df6a321d9a39 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -10,6 +10,10 @@ ### 💡 Others +## 56.0.1 — 2026-05-21 + +_This version does not introduce any user-facing changes._ + ## 56.0.0 — 2026-05-20 ### 🎉 New features diff --git a/packages/expo/android/build.gradle b/packages/expo/android/build.gradle index d07369d1d6deca..32e2cf964a966e 100644 --- a/packages/expo/android/build.gradle +++ b/packages/expo/android/build.gradle @@ -10,7 +10,7 @@ buildscript { } group = 'host.exp.exponent' -version = '56.0.0' +version = '56.0.1' expoModule { // We can't prebuild the module because it depends on the generated files. @@ -21,7 +21,7 @@ android { namespace "expo.core" defaultConfig { versionCode 1 - versionName "56.0.0" + versionName "56.0.1" consumerProguardFiles("proguard-rules.pro") } testOptions { diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 32e3284e0039ea..8beda47727442b 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -69,7 +69,7 @@ "expo-navigation-bar": "~56.0.3", "expo-network": "~56.0.4", "expo-notifications": "~56.0.11", - "expo-observe": "~56.0.11", + "expo-observe": "~56.0.12", "expo-print": "~56.0.3", "expo-live-photo": "~56.0.3", "expo-router": "~56.2.3", diff --git a/packages/expo/package.json b/packages/expo/package.json index b1d227e0b96d3a..d70599cb39feb1 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -1,6 +1,6 @@ { "name": "expo", - "version": "56.0.0", + "version": "56.0.1", "description": "The Expo SDK", "main": "src/Expo.ts", "module": "src/Expo.ts", @@ -75,7 +75,7 @@ "homepage": "https://github.com/expo/expo/tree/main/packages/expo", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "workspace:^56.1.7", + "@expo/cli": "workspace:^56.1.8", "@expo/config": "workspace:~56.0.8", "@expo/config-plugins": "workspace:~56.0.7", "@expo/devtools": "workspace:~56.0.2", @@ -92,7 +92,7 @@ "expo-file-system": "workspace:~56.0.7", "expo-font": "workspace:~56.0.5", "expo-keep-awake": "workspace:~56.0.3", - "expo-modules-autolinking": "workspace:~56.0.9", + "expo-modules-autolinking": "workspace:~56.0.10", "expo-modules-core": "workspace:~56.0.11", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b283c0d1664b4..5e7194cfb9fe1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1849,7 +1849,7 @@ importers: specifier: workspace:^0.7.0 version: link:../plist '@expo/prebuild-config': - specifier: workspace:^56.0.10 + specifier: workspace:^56.0.11 version: link:../prebuild-config '@expo/require-utils': specifier: workspace:^56.1.2 @@ -2831,7 +2831,7 @@ importers: specifier: ^4.3.1 version: 4.4.3 expo-modules-autolinking: - specifier: workspace:~56.0.9 + specifier: workspace:~56.0.10 version: link:../../expo-modules-autolinking resolve-from: specifier: ^5.0.0 @@ -3464,7 +3464,7 @@ importers: specifier: ^7.20.0 version: 7.29.2 '@expo/cli': - specifier: workspace:^56.1.7 + specifier: workspace:^56.1.8 version: link:../@expo/cli '@expo/config': specifier: workspace:~56.0.8 @@ -3515,7 +3515,7 @@ importers: specifier: workspace:~56.0.3 version: link:../expo-keep-awake expo-modules-autolinking: - specifier: workspace:~56.0.9 + specifier: workspace:~56.0.10 version: link:../expo-modules-autolinking expo-modules-core: specifier: workspace:~56.0.11 diff --git a/templates/expo-template-bare-minimum/package.json b/templates/expo-template-bare-minimum/package.json index b78f89ac34fe01..42666fee9c6e75 100644 --- a/templates/expo-template-bare-minimum/package.json +++ b/templates/expo-template-bare-minimum/package.json @@ -2,7 +2,7 @@ "name": "expo-template-bare-minimum", "description": "This bare project template includes a minimal setup for using unimodules with React Native.", "license": "0BSD", - "version": "56.0.14", + "version": "56.0.15", "main": "index.js", "scripts": { "start": "expo start --dev-client", @@ -11,7 +11,7 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~56.0.0", + "expo": "~56.0.1", "expo-status-bar": "~56.0.4", "react": "19.2.3", "react-native": "0.85.3" diff --git a/templates/expo-template-blank-typescript/package.json b/templates/expo-template-blank-typescript/package.json index 22bf639f682511..0896cc36fa05f8 100644 --- a/templates/expo-template-blank-typescript/package.json +++ b/templates/expo-template-blank-typescript/package.json @@ -2,7 +2,7 @@ "name": "expo-template-blank-typescript", "description": "The Blank project template includes the minimum dependencies to run and an empty root component.", "license": "0BSD", - "version": "56.0.14", + "version": "56.0.15", "main": "index.ts", "scripts": { "start": "expo start", @@ -11,7 +11,7 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~56.0.0", + "expo": "~56.0.1", "expo-status-bar": "~56.0.4", "react": "19.2.3", "react-native": "0.85.3" diff --git a/templates/expo-template-blank/package.json b/templates/expo-template-blank/package.json index 86ac5b414a9914..6e63939fdbcee2 100644 --- a/templates/expo-template-blank/package.json +++ b/templates/expo-template-blank/package.json @@ -2,7 +2,7 @@ "name": "expo-template-blank", "description": "The Blank project template includes the minimum dependencies to run and an empty root component.", "license": "0BSD", - "version": "56.0.14", + "version": "56.0.15", "main": "index.js", "scripts": { "start": "expo start", @@ -11,7 +11,7 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~56.0.0", + "expo": "~56.0.1", "expo-status-bar": "~56.0.4", "react": "19.2.3", "react-native": "0.85.3" diff --git a/templates/expo-template-default/package.json b/templates/expo-template-default/package.json index bc598f6dd22b1a..97640513826dde 100644 --- a/templates/expo-template-default/package.json +++ b/templates/expo-template-default/package.json @@ -2,7 +2,7 @@ "name": "expo-template-default", "license": "0BSD", "main": "expo-router/entry", - "version": "56.0.14", + "version": "56.0.15", "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", @@ -13,7 +13,7 @@ }, "dependencies": { "@expo/ui": "~56.0.10", - "expo": "~56.0.0", + "expo": "~56.0.1", "expo-constants": "~56.0.13", "expo-device": "~56.0.4", "expo-font": "~56.0.5", diff --git a/templates/expo-template-tabs/package.json b/templates/expo-template-tabs/package.json index 297eea325bf3c9..9b4f531714ba51 100644 --- a/templates/expo-template-tabs/package.json +++ b/templates/expo-template-tabs/package.json @@ -3,7 +3,7 @@ "main": "expo-router/entry", "description": "The Tab Navigation project template includes several example screens.", "license": "0BSD", - "version": "56.0.14", + "version": "56.0.15", "scripts": { "start": "expo start", "android": "expo start --android", @@ -11,7 +11,7 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~56.0.0", + "expo": "~56.0.1", "expo-constants": "~56.0.13", "expo-font": "~56.0.5", "expo-linking": "~56.0.10", From e684272210faedf6c76dad9f3724f33783c483c6 Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Thu, 21 May 2026 11:41:12 +0200 Subject: [PATCH 8/9] [expo-router][docs] Document how to use @expo/material-symbols in toolbar (#45861) # Why # How # Test Plan # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- docs/pages/router/advanced/stack-toolbar.mdx | 38 +++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/pages/router/advanced/stack-toolbar.mdx b/docs/pages/router/advanced/stack-toolbar.mdx index 55ae168b01f1aa..3be6c0bb438d7a 100644 --- a/docs/pages/router/advanced/stack-toolbar.mdx +++ b/docs/pages/router/advanced/stack-toolbar.mdx @@ -4,6 +4,7 @@ description: Learn how to use the native toolbar in Stack navigation with Expo R --- import { Collapsible } from '~/ui/components/Collapsible'; +import { Terminal } from '~/ui/components/Snippet'; import { Tabs, Tab } from '~/ui/components/Tabs'; > **important** `Stack.Toolbar` is an [alpha](/more/release-statuses/#alpha) API available on Android in **Expo SDK 56** and later, and on iOS in **Expo SDK 55** and later. The API is subject to breaking changes. @@ -109,6 +110,41 @@ You can browse available symbols in Apple's SF Symbols app. > **info** SF Symbols are an iOS-only feature. +### Material Symbols (Android only) + +The recommended source of icons for Android is [`@expo/material-symbols`](https://www.npmjs.com/package/@expo/material-symbols) library. It ships Google's [Material Symbols](https://fonts.google.com/icons) as individual asset subpaths, so Metro only bundles the icons you actually import. + + + +Import any icon directly from its own subpath and pass it to the `icon` prop: + +```tsx +import Star from '@expo/material-symbols/star.xml'; +import Share from '@expo/material-symbols/share.xml'; +import MoreVert from '@expo/material-symbols/more_vert.xml'; + + {}} /> + {}} /> +{/* ... */} +``` + +Vector drawables are tinted with the toolbar's tint color by default. Pass `iconRenderingMode="original"` to preserve the source colors. + +> **info** Material Symbols XML drawables are an Android-only feature. On iOS, use SF Symbols instead. + +#### Using the same icon on Android and iOS + +`Stack.Toolbar.Button`'s `icon` prop accepts both an `ImageSourcePropType` (Android) and an SF Symbol name (iOS). To use a single component for both platforms, branch on `process.env.EXPO_OS` and pass the platform-appropriate value. Metro replaces `process.env.EXPO_OS` with a string literal at build time, then tree-shakes the branch that doesn't match the current platform — so the Material Symbols XML drawable never ships in the iOS bundle, and the SF Symbol name never ships in the Android bundle: + +```tsx +import Star from '@expo/material-symbols/star.xml'; + + {}} +/>; +``` + ### Custom images You can also use custom images. The API for passing them differs by platform: @@ -711,7 +747,7 @@ export default function Home() { - + On Android, `icon` must be an `ImageSourcePropType`. For example `require('./icon.png')` or `{ uri: '...' }`. From 22a899384989aa008fb95a34dcf3dc508e8f929b Mon Sep 17 00:00:00 2001 From: Jakub Tkacz <32908614+Ubax@users.noreply.github.com> Date: Thu, 21 May 2026 11:41:21 +0200 Subject: [PATCH 9/9] [expo-router][docs] Remove references to react-navigation (#46072) # Why # How # Test Plan # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --- docs/pages/develop/app-navigation.mdx | 2 +- docs/pages/router/advanced/native-tabs.mdx | 14 ++-- docs/pages/router/advanced/stack-toolbar.mdx | 12 ++-- docs/pages/router/advanced/stack.mdx | 71 ++++--------------- docs/pages/router/basics/navigation.mdx | 2 +- .../static/data/unversioned/expo-router.json | 2 +- .../data/unversioned/expo-router/link.json | 2 +- .../data/unversioned/expo-router/stack.json | 2 +- .../static/data/v56.0.0/expo-router.json | 2 +- .../static/data/v56.0.0/expo-router/link.json | 2 +- .../data/v56.0.0/expo-router/native-tabs.json | 2 +- .../data/v56.0.0/expo-router/stack.json | 2 +- packages/expo-router/CHANGELOG.md | 2 + .../build/global-state/router.d.ts | 7 +- .../build/global-state/router.d.ts.map | 2 +- .../build/global-state/router.js.map | 2 +- .../build/hooks/useRootNavigationState.d.ts | 9 ++- .../hooks/useRootNavigationState.d.ts.map | 2 +- .../build/hooks/useRootNavigationState.js | 9 ++- .../build/hooks/useRootNavigationState.js.map | 2 +- .../expo-router/build/useFocusEffect.d.ts | 7 +- .../expo-router/build/useFocusEffect.d.ts.map | 2 +- packages/expo-router/build/useFocusEffect.js | 11 +-- .../expo-router/build/useFocusEffect.js.map | 2 +- packages/expo-router/build/useNavigation.d.ts | 10 +-- .../expo-router/build/useNavigation.d.ts.map | 2 +- packages/expo-router/build/useNavigation.js | 10 +-- .../expo-router/build/useNavigation.js.map | 2 +- .../expo-router/src/global-state/router.ts | 7 +- .../src/hooks/useRootNavigationState.ts | 9 ++- packages/expo-router/src/useFocusEffect.ts | 11 +-- packages/expo-router/src/useNavigation.ts | 10 +-- 32 files changed, 112 insertions(+), 121 deletions(-) diff --git a/docs/pages/develop/app-navigation.mdx b/docs/pages/develop/app-navigation.mdx index 10bfd399e29bb5..6d33cd373a42c5 100644 --- a/docs/pages/develop/app-navigation.mdx +++ b/docs/pages/develop/app-navigation.mdx @@ -31,7 +31,7 @@ The library offers platform-specific look-and-feel with smooth animations and ge Expo Router is a file-based routing library for Expo and React Native projects. By following the **app** directory convention, it turns files into routes and is integrated with Expo for [Expo CLI](/more/expo-cli/) and bundling without additional setup. The library also adds features such as typed routes, dynamic routes, lazy bundling in development, static rendering for the web, and automatic deep linking. -New Expo projects created with `npx create-expo-app@latest --template default@sdk-55` include Expo Router by default so you can ship cross-platform navigation quickly while still being able to reach for React Navigation APIs when needed. +New Expo projects created with `npx create-expo-app@latest --template default@sdk-55` include Expo Router by default. -This happens because React Navigation's default theme uses a white background color. To fix this, wrap your app in React Navigation's `ThemeProvider` with the appropriate theme. +This happens because the default theme uses a white background color. To fix this, wrap your app in Expo Router's `ThemeProvider` with the appropriate theme. **For apps supporting both light and dark modes:** ```tsx src/app/_layout.tsx -import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; +import { ThemeProvider, DarkTheme, DefaultTheme } from 'expo-router'; import { NativeTabs } from 'expo-router/unstable-native-tabs'; import { useColorScheme } from 'react-native'; @@ -1373,7 +1373,7 @@ export default function TabLayout() { **For dark-mode-only apps:** ```tsx src/app/_layout.tsx -import { ThemeProvider, DarkTheme } from '@react-navigation/native'; +import { ThemeProvider, DarkTheme } from 'expo-router'; import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -1417,14 +1417,14 @@ export default function HomeScreen() { -Header buttons with liquid glass styling may flicker or flash their background when switching tabs in dark mode on iOS 26. This happens because React Navigation's default theme doesn't match the system dark mode, causing visual artifacts in the liquid glass rendering. +Header buttons with liquid glass styling may flicker or flash their background when switching tabs in dark mode on iOS 26. This happens because the default theme doesn't match the system dark mode, causing visual artifacts in the liquid glass rendering. -The fix is the same as for the white background flash issue: wrap your layout with `` from `@react-navigation/native` using the appropriate theme. +The fix is the same as for the white background flash issue: wrap your layout with `` from `expo-router` using the appropriate theme. **For apps supporting both light and dark modes:** ```tsx app/_layout.tsx -import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; +import { ThemeProvider, DarkTheme, DefaultTheme } from 'expo-router'; import { NativeTabs } from 'expo-router/unstable-native-tabs'; import { useColorScheme } from 'react-native'; @@ -1449,7 +1449,7 @@ export default function TabLayout() { **For dark-mode-only apps:** ```tsx app/_layout.tsx -import { ThemeProvider, DarkTheme } from '@react-navigation/native'; +import { ThemeProvider, DarkTheme } from 'expo-router'; import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { diff --git a/docs/pages/router/advanced/stack-toolbar.mdx b/docs/pages/router/advanced/stack-toolbar.mdx index 3be6c0bb438d7a..303e60e5cbdd2b 100644 --- a/docs/pages/router/advanced/stack-toolbar.mdx +++ b/docs/pages/router/advanced/stack-toolbar.mdx @@ -645,13 +645,12 @@ export default function DocumentScreen() { -Toolbar buttons with liquid glass styling may flicker or flash their background when navigating between screens in dark mode on iOS 26. This happens because React Navigation's default theme doesn't match the system dark mode, causing visual artifacts in the liquid glass rendering. +Toolbar buttons with liquid glass styling may flicker or flash their background when navigating between screens in dark mode on iOS 26. This happens because the default theme doesn't match the system dark mode, causing visual artifacts in the liquid glass rendering. -To fix this, wrap your root layout with `` from `@react-navigation/native` using the appropriate theme: +To fix this, wrap your root layout with `` from `expo-router` using the appropriate theme: ```tsx src/app/_layout.tsx -import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; -import { Stack } from 'expo-router'; +import { ThemeProvider, DarkTheme, DefaultTheme, Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; export default function RootLayout() { @@ -673,11 +672,10 @@ export default function RootLayout() { A white flash between screen transitions usually means the navigation stack is using a light background while your app uses a dark theme. This is especially noticeable when screens contain toolbar items, as the flash contrasts with the toolbar styling. -To fix this, wrap your root layout with React Navigation's `` and pass the appropriate theme: +To fix this, wrap your root layout with Expo Router's `` and pass the appropriate theme: ```tsx src/app/_layout.tsx -import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; -import { Stack } from 'expo-router'; +import { ThemeProvider, DarkTheme, DefaultTheme, Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; export default function RootLayout() { diff --git a/docs/pages/router/advanced/stack.mdx b/docs/pages/router/advanced/stack.mdx index 8a1f79173b6500..653b4cb16c72fe 100644 --- a/docs/pages/router/advanced/stack.mdx +++ b/docs/pages/router/advanced/stack.mdx @@ -473,57 +473,6 @@ export default function Settings() { } ``` -## Relation with Native Stack Navigator - -The `Stack` navigator in Expo Router wraps the [Native Stack Navigator](https://reactnavigation.org/docs/native-stack-navigator) from React Navigation. Options available in the Native Stack Navigator are all available in the `Stack` navigator in Expo Router. - -### JavaScript stack with @react-navigation/stack - -You can also use the JavaScript-powered `@react-navigation/stack` library to create a custom layout component by wrapping this library with the `withLayoutContext`. - -In the following example, `JsStack` component is defined using `@react-navigation/stack` library: - -```tsx layouts/js-stack.tsx -import { ParamListBase, StackNavigationState } from '@react-navigation/native'; -import { - createStackNavigator, - StackNavigationEventMap, - StackNavigationOptions, -} from '@react-navigation/stack'; -import { withLayoutContext } from 'expo-router'; - -const { Navigator } = createStackNavigator(); - -export const JsStack = withLayoutContext< - StackNavigationOptions, - typeof Navigator, - StackNavigationState, - StackNavigationEventMap ->(Navigator); -``` - -After defining the `JsStack` component, you can use it in your app: - -{/* prettier-ignore */} -```tsx src/app/_layout.tsx -import { JsStack } from '../layouts/js-stack'; - -export default function Layout() { - return ( - - ); -} -``` - -For more information on available options, see [`@react-navigation/stack` documentation](https://reactnavigation.org/docs/stack-navigator). - ## iOS 26 Liquid Glass headers Starting from iOS 26, navigation headers adopt the system's "Liquid Glass" effect by default. It cannot be disabled per screen, so you need to opt out using a global configuration. @@ -546,9 +495,17 @@ Create a [development build](/develop/development-builds/create-a-build/#prerequ ### Method 2: Use JavaScript-based navigation stack -Switch from native navigation library ([`@react-navigation/native`](https://reactnavigation.org/docs/native-stack-navigator/)) to a JavaScript-based stack navigator library such as [`@react-navigation/stack`](https://reactnavigation.org/docs/stack-navigator/), which gives you full control over the header UI but at the cost of performance benefits of using the highly optimized iOS navigation views/controllers. +Swap the native stack for the JavaScript-driven stack shipped at `expo-router/js-stack`. This gives you full control over the header UI, at the cost of the performance benefits of the highly optimized iOS navigation views and controllers: -For more information, see [JavaScript stack with `@react-navigation/stack`](#javascript-stack-with-react-navigationstack). +```tsx src/app/_layout.tsx +import { Stack as JsStack } from 'expo-router/js-stack'; + +export default function Layout() { + return ; +} +``` + +The `expo-router/js-stack` entry point is the SDK 56 replacement for the `@react-navigation/stack` library. See the [SDK 55 to 56 migration guide](/router/migrate/sdk-55-to-56) for more information. ## Common problems @@ -600,11 +557,10 @@ export default function Home() { A white flash between screen transitions usually means the navigation stack is using a light background while your app uses a dark theme. -To fix this, wrap your root layout with React Navigation's `` and pass the appropriate theme: +To fix this, wrap your root layout with Expo Router's `` and pass the appropriate theme: ```tsx src/app/_layout.tsx -import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; -import { Stack } from 'expo-router'; +import { ThemeProvider, DarkTheme, DefaultTheme, Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; export default function RootLayout() { @@ -623,8 +579,7 @@ export default function RootLayout() { For apps that are always dark-themed: ```tsx src/app/_layout.tsx -import { ThemeProvider, DarkTheme } from '@react-navigation/native'; -import { Stack } from 'expo-router'; +import { ThemeProvider, DarkTheme, Stack } from 'expo-router'; export default function RootLayout() { return ( diff --git a/docs/pages/router/basics/navigation.mdx b/docs/pages/router/basics/navigation.mdx index f8be79bd430663..14086ea20f79fb 100644 --- a/docs/pages/router/basics/navigation.mdx +++ b/docs/pages/router/basics/navigation.mdx @@ -11,7 +11,7 @@ import { BoxLink } from '~/ui/components/BoxLink'; import { FileTree } from '~/ui/components/FileTree'; import { VideoBoxLink } from '~/ui/components/VideoBoxLink'; -Once you have a few pages in your app and their layouts setup, it's time to start navigating between them. Navigation in Expo Router works a lot like React Navigation, but with all pages having a URL by default, we can create links and use these URLs to move about our app using familiar web patterns. +Once you have a few pages in your app and their layouts setup, it's time to start navigating between them. Every page in Expo Router has a URL by default, so you can navigate between pages using links and the same URL patterns you use on the web. ## Native navigation basics with `useRouter` diff --git a/docs/public/static/data/unversioned/expo-router.json b/docs/public/static/data/unversioned/expo-router.json index 60dc5c3297cf23..70d5f4cabfb5d9 100644 --- a/docs/public/static/data/unversioned/expo-router.json +++ b/docs/public/static/data/unversioned/expo-router.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-router","variant":"project","kind":1,"children":[{"name":"ActionDispatchedEvent","variant":"declaration","kind":256,"children":[{"name":"actionType","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The action type from the dispatched NavigationAction (e.g. "},{"kind":"code","text":"`NAVIGATE`"},{"kind":"text","text":")."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"payload","variant":"declaration","kind":1024,"type":{"type":"union","types":[{"type":"intrinsic","name":"object"},{"type":"intrinsic","name":"undefined"}]}},{"name":"state","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/global-state/types.ts","qualifiedName":"ReactNavigationState"},"name":"ReactNavigationState","package":"expo-router"}},{"name":"type","variant":"declaration","kind":1024,"type":{"type":"literal","value":"actionDispatched"}}]},{"name":"AnalyticsEvent","variant":"declaration","kind":2097152,"type":{"type":"union","types":[{"type":"reference","name":"PagePreloadedEvent","package":"expo-router"},{"type":"reference","name":"PageFocusedEvent","package":"expo-router"},{"type":"reference","name":"PageBlurredEvent","package":"expo-router"},{"type":"reference","name":"PageRemoved","package":"expo-router"},{"type":"reference","name":"ActionDispatchedEvent","package":"expo-router"}]}},{"name":"Badge","variant":"declaration","kind":64,"signatures":[{"name":"Badge","variant":"signature","kind":4096,"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"reference","name":"BadgeProps","package":"expo-router"}}],"type":{"type":"literal","value":null}}]},{"name":"BadgeProps","variant":"declaration","kind":2097152,"type":{"type":"union","types":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/native-tabs/common/elements.tsx","qualifiedName":"NativeTabsTriggerBadgeProps"},"name":"NativeTabsTriggerBadgeProps","package":"expo-router"},{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/layouts/stack-utils/toolbar/toolbar-primitives.tsx","qualifiedName":"StackToolbarBadgeProps"},"name":"StackToolbarBadgeProps","package":"expo-router"}]}},{"name":"DarkTheme","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/native/types.tsx","qualifiedName":"Theme"},"name":"Theme","package":"expo-router"},"defaultValue":"..."},{"name":"DefaultTheme","variant":"declaration","kind":32,"flags":{"isConst":true},"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/native/types.tsx","qualifiedName":"Theme"},"name":"Theme","package":"expo-router"},"defaultValue":"..."},{"name":"EffectCallback","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Memoized callback containing the effect, should optionally return a cleanup function."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"union","types":[{"type":"intrinsic","name":"undefined"},{"type":"intrinsic","name":"void"},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"intrinsic","name":"void"}}]}}]}}]}}},{"name":"ErrorBoundary","variant":"declaration","kind":64,"signatures":[{"name":"ErrorBoundary","variant":"signature","kind":4096,"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","name":"ErrorBoundaryProps","package":"expo-router"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]},{"name":"ErrorBoundaryProps","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Props passed to a page's "},{"kind":"code","text":"`ErrorBoundary`"},{"kind":"text","text":" export."}]},"children":[{"name":"error","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The error that was thrown."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Error"},"name":"Error","package":"typescript"}},{"name":"retry","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"A function that will re-render the route component by clearing the "},{"kind":"code","text":"`error`"},{"kind":"text","text":" state."}]},"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]}}}]},{"name":"ExperimentalStack","variant":"declaration","kind":32,"flags":{"isConst":true},"comment":{"summary":[{"kind":"text","text":"Renders the new "},{"kind":"code","text":"`react-native-screens/experimental`"},{"kind":"text","text":" native stack.\n\nSibling to "},{"kind":"code","text":"`Stack`"},{"kind":"text","text":". Native-only — on web it falls back to the standard "},{"kind":"code","text":"`Stack`"},{"kind":"text","text":".\nOpt-in per navigator: replace "},{"kind":"code","text":"``"},{"kind":"text","text":" with "},{"kind":"code","text":"``"},{"kind":"text","text":" in the\nspecific layout you want to migrate."}],"modifierTags":["@experimental"]},"type":{"type":"intersection","types":[{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"props","variant":"param","kind":32768,"type":{"type":"intersection","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Omit"},"typeArguments":[{"type":"intersection","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Omit"},"typeArguments":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/layouts/experimental-stack/types.ts","qualifiedName":"ExperimentalStackNavigatorProps"},"name":"ExperimentalStackNavigatorProps","package":"expo-router"},{"type":"union","types":[{"type":"literal","value":"children"},{"type":"literal","value":"initialRouteName"},{"type":"literal","value":"layout"},{"type":"literal","value":"screenListeners"},{"type":"literal","value":"screenOptions"},{"type":"literal","value":"screenLayout"},{"type":"literal","value":"UNSTABLE_router"},{"type":"literal","value":"UNSTABLE_routeNamesChangeBehavior"},{"type":"literal","value":"id"}]}],"name":"Omit","package":"typescript"},{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/types.tsx","qualifiedName":"DefaultRouterOptions"},"typeArguments":[{"type":"intrinsic","name":"string"}],"name":"DefaultRouterOptions","package":"expo-router"},{"type":"unknown","name":"{ children: ReactNode; layout?: ((props: { state: StackNavigationState; navigation: NavigationHelpers; descriptors: Record<...>; children: ReactNode; }) => ReactElement<...>) | undefined; ... 4 more ...; UNSTABLE_routeNamesChangeBehavior?: \"firstMatch\" | ... 1 more ... | undefined; ..."}]},{"type":"literal","value":"children"}],"name":"Omit","package":"typescript"},{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Partial"},"typeArguments":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Pick"},"typeArguments":[{"type":"intersection","types":[{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Omit"},"typeArguments":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/layouts/experimental-stack/types.ts","qualifiedName":"ExperimentalStackNavigatorProps"},"name":"ExperimentalStackNavigatorProps","package":"expo-router"},{"type":"union","types":[{"type":"literal","value":"children"},{"type":"literal","value":"initialRouteName"},{"type":"literal","value":"layout"},{"type":"literal","value":"screenListeners"},{"type":"literal","value":"screenOptions"},{"type":"literal","value":"screenLayout"},{"type":"literal","value":"UNSTABLE_router"},{"type":"literal","value":"UNSTABLE_routeNamesChangeBehavior"},{"type":"literal","value":"id"}]}],"name":"Omit","package":"typescript"},{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/react-navigation/routers/types.tsx","qualifiedName":"DefaultRouterOptions"},"typeArguments":[{"type":"intrinsic","name":"string"}],"name":"DefaultRouterOptions","package":"expo-router"},{"type":"unknown","name":"{ children: ReactNode; layout?: ((props: { state: StackNavigationState; navigation: NavigationHelpers; descriptors: Record<...>; children: ReactNode; }) => ReactElement<...>) | undefined; ... 4 more ...; UNSTABLE_routeNamesChangeBehavior?: \"firstMatch\" | ... 1 more ... | undefined; ..."}]},{"type":"literal","value":"children"}],"name":"Pick","package":"typescript"}],"name":"Partial","package":"typescript"},{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.RefAttributes"},"typeArguments":[{"type":"intrinsic","name":"unknown"}],"name":"RefAttributes","package":"@types/react","qualifiedName":"React.RefAttributes"}]}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"Protected","variant":"declaration","kind":1024,"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"index.d.ts","qualifiedName":"React.FunctionComponent"},"typeArguments":[{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/views/Protected.tsx","qualifiedName":"ProtectedProps"},"name":"ProtectedProps","package":"expo-router"}],"name":"FunctionComponent","package":"@types/react","qualifiedName":"React.FunctionComponent"}},{"name":"Screen","variant":"declaration","kind":1024,"type":{"type":"intersection","types":[{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"parameters":[{"name":"__namedParameters","variant":"param","kind":32768,"type":{"type":"reference","target":{"packageName":"expo-router","packagePath":"src/layouts/stack-utils/StackScreen.tsx","qualifiedName":"StackScreenProps"},"name":"StackScreenProps","package":"expo-router"}}],"type":{"type":"reference","target":{"packageName":"@types/react","packagePath":"jsx-runtime.d.ts","qualifiedName":"JSX.Element"},"name":"Element","package":"@types/react","qualifiedName":"JSX.Element"}}]}},{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"BackButton","variant":"declaration","kind":1024,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"signatures":[{"name":"__type","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Component to configure the back button.\n\nCan be used inside Stack.Screen in a layout or directly inside a screen component."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { Stack } from 'expo-router';\n\nexport default function Layout() {\n return (\n \n \n Back\n \n \n );\n}\n```"}]},{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { Stack } from 'expo-router';\n\nexport default function Page() {\n return (\n <>\n