Skip to content

feat: expo upgrade from SDK 52 to 54#129

Merged
isotronic merged 23 commits into
masterfrom
expo-upgrade
May 6, 2026
Merged

feat: expo upgrade from SDK 52 to 54#129
isotronic merged 23 commits into
masterfrom
expo-upgrade

Conversation

@isotronic
Copy link
Copy Markdown
Owner

@isotronic isotronic commented May 6, 2026

Summary by Sourcery

Upgrade the app to Expo SDK 54 and align the project with the new React Native/React ecosystem versions and routing behaviors.

New Features:

  • Enable richer notification presentation on supported platforms with banner and list visibility options.

Bug Fixes:

  • Correct various expo-router navigation paths and typings to match the current routed app structure, preventing misnavigation after onboarding and in plan/stat flows.

Enhancements:

  • Update Expo, React Native, React, and related Expo modules and libraries to versions compatible with Expo SDK 54, including Bugsnag, navigation, testing, and tooling dependencies.
  • Adjust router and link typings to use the new Href and ExternalPathString types where appropriate.
  • Replace use of the splash screen hide API and layout options to reflect the latest Expo Router and SplashScreen behaviors.
  • Swap the Reanimated Babel plugin for the react-native-worklets plugin and switch TypeScript to bundler module resolution, updating ESLint config for the newer Expo tooling.
  • Refine dropdown and workout card components to align with updated libraries and APIs, including removing the Sortable wrapper where no longer supported.

Build:

  • Bump app version and Android versionCode and update Android compile/target SDK versions to 36.

Tests:

  • Update testing dependencies to versions compatible with Expo SDK 54 and React 19 and adjust timeout typing in unit tests.

Summary by CodeRabbit

  • New Features

    • Expanded onboarding with new cards for custom plans and ready‑made plans.
  • Bug Fixes / UX

    • Improved navigation flows across plan creation, plans tab and stats screens.
    • Improved image selection for custom exercises.
    • Splash screen startup handling improved for more reliable app launch.
  • Chores

    • Bumped app version to 0.19.0 and Android API level to 36.
    • Updated dependencies and developer tooling.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 6, 2026

Reviewer's Guide

Upgrades the app from Expo SDK 52 to 54 (including React Native/React and related libraries) and aligns routing, configuration, notifications, Babel, and TypeScript settings with the new Expo/Expo Router APIs and platform requirements.

File-Level Changes

Change Details Files
Upgrade Expo SDK, React Native/React, and related runtime dependencies to versions compatible with Expo 54.
  • Bump expo from 52.x to 54.x and update all first‑party Expo packages (expo-application, expo-asset, expo-av, etc.) to their corresponding 54-compatible versions
  • Upgrade React and React DOM from 18.3.x to 19.1.0 and React Native from 0.76.x to 0.81.5
  • Update various ecosystem libraries to versions compatible with the new Expo/React Native runtime (e.g. @bugsnag/expo, @expo/vector-icons, @gorhom/bottom-sheet, @shopify/flash-list, react-native-gesture-handler, react-native-reanimated, react-native-safe-area-context, react-native-screens, react-native-svg, react-native-web, react-native-worklets)
package.json
package-lock.json
Align dev/test tooling with the new runtime (Jest/Expo testing, typings, ESLint, TypeScript).
  • Upgrade jest-expo to the Expo 54 preset and keep Jest core at 29.x
  • Bump @testing-library/react-native and react-test-renderer to React 19-compatible versions
  • Update @types/react and @types/react-test-renderer to 19.x
  • Upgrade eslint-config-expo to ~10.0.0 and add a rule override to disable expo/use-dom-exports
  • Upgrade TypeScript from ~5.3 to ~5.9 and adjust tsconfig moduleResolution from node to bundler
package.json
.eslintrc.js
tsconfig.json
hooks/__tests__/useAnimatedImageQuery.test.ts
Update navigation and routing code to use Expo Router 6 path conventions and stronger typing.
  • Change onboarding routes to the new nested / (app)/(tabs)/... structure and type routes as Href
  • Update various router.push usages to use object form with pathname and params instead of string interpolation where required by Expo Router 6
  • Adjust ExternalLink component to type href as ExternalPathString instead of string
components/Onboarding.tsx
app/(app)/(tabs)/(plans)/overview.tsx
app/(app)/(create-plan)/create.tsx
app/(app)/(tabs)/(stats)/history-details.tsx
components/ExternalLink.tsx
Adjust app initialization, layout, and platform configuration for the new Expo SDK and Android requirements.
  • Bump app version and Android versionCode in app.config.js and update Android compileSdkVersion/targetSdkVersion from 35 to 36
  • Remove the global SplashScreen.preventAutoHideAsync call and switch to SplashScreen.hide() in the RootLayout effect to match new splash API
  • Remove custom screenOptions from so that header behavior is driven by Expo Router defaults
app.config.js
app/_layout.tsx
Adapt notification handling to new expo-notifications API capabilities.
  • Extend notification handler configuration to include shouldShowBanner and shouldShowList flags in addition to alert/sound/badge
utils/notificationSetup.ts
Update Babel and animation-related configuration to use react-native-worklets instead of react-native-reanimated’s Babel plugin and adjust code using Sortable components.
  • Switch Babel plugin from react-native-reanimated/plugin to react-native-worklets/plugin
  • Add react-native-worklets dependency
  • Replace Sortable.Pressable wrapper usage in WorkoutCard with a standard View to align with updated sortable API or compatibility constraints
babel.config.js
package.json
components/WorkoutCard.tsx
Clean up minor type-level and generics usages to satisfy updated library typings and TS 5.9.
  • Remove explicit generic parameter from Dropdown usages where it is no longer required or supported
  • Adjust jest.spyOn(global, "setTimeout") mock to return ReturnType instead of NodeJS.Timeout for type correctness
components/FilterRow.tsx
hooks/__tests__/useAnimatedImageQuery.test.ts
Misc project file touch-ups to align with new runtime (no behavioral changes apparent in diff snippet).
  • Update or regenerate helper utilities and app files affected by dependency changes (backup, clearUserData, downloadAllAnimatedImages, initAppDataDB, and custom-exercise)
app/(app)/custom-exercise.tsx
hooks/useAnimatedImageQuery.ts
utils/backup.ts
utils/clearUserData.ts
utils/downloadAllAnimatedImages.ts
utils/initAppDataDB.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR updates build/config files and dependencies, migrates expo-file-system imports to the legacy entry, adjusts nested app routing and onboarding routes, changes a few component typings and render wrappers, expands Jest file-system mocks, and updates notification presentation options.

Changes

Upgrade, FileSystem Migration & Routing

Layer / File(s) Summary
Configuration / Versioning
.eslintrc.js, babel.config.js, tsconfig.json, app.config.js
Added ESLint rule "expo/use-dom-exports": "off"; changed Babel plugin to react-native-worklets/plugin; tsconfig moduleResolution set to "bundler"; Expo app version bumped to 0.19.0, Android versionCode to 1900, and compile/target SDK set to 36 in build properties.
Dependency Manifest
package.json
Large dependency and devDependency updates/additions (Bugsnag, Expo/React Native ecosystem packages, testing/tooling updates).
FileSystem API Migration
hooks/useAnimatedImageQuery.ts, app/(app)/custom-exercise.tsx, utils/backup.ts, utils/clearUserData.ts, utils/downloadAllAnimatedImages.ts, utils/initAppDataDB.ts
Replaced imports from expo-file-system with expo-file-system/legacy across hooks, components and utilities. Also adjusted ImagePicker mediaTypes to ["images"] in custom-exercise.
Routing / Navigation Wiring
app/(app)/(create-plan)/create.tsx, app/(app)/(tabs)/(plans)/overview.tsx, app/(app)/(tabs)/(stats)/history-details.tsx, components/Onboarding.tsx
Migrated routes to nested app layout (e.g., /(app)/(create-plan)/image-search, /(app)/(tabs)/(plans)); Edit handler switched to object-based router.push({ pathname, params }); Onboarding routes converted to Href types and data updated.
Component Type & Render Adjustments
components/ExternalLink.tsx, components/FilterRow.tsx, components/WorkoutCard.tsx
ExternalLink.href typed as ExternalPathString; removed explicit <Dropdown<OptionItem>> generics in FilterRow; WorkoutCard now uses a plain View wrapping Sortable.Touchable (onTap) instead of Sortable.Pressable + TouchableOpacity.
Splash / Layout Changes
app/_layout.tsx
Moved SplashScreen.preventAutoHideAsync() to after QueryClient init with .catch(() => {}); replaced SplashScreen.hideAsync() with SplashScreen.hide(); removed explicit headerShown Slot option.
Notification Presentation
utils/notificationSetup.ts
handleNotification now includes shouldShowBanner: true and shouldShowList: true in its return object.
Jest Setup & Tests
jestSetupFile.js, hooks/__tests__/useAnimatedImageQuery.test.ts, utils/__tests__/*
Expanded and more robust mocks for expo-file-system and added a expo-file-system/legacy mock; added DevMenu and extended Google Sign-In mocks; updated tests and mocks to import/mock the legacy FileSystem and adjusted useAnimatedImageQuery test to use gcTime in options.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
Through nested paths I lightly tread,
Legacy files hop where versions led,
Config and mocks in tidy rows,
Banners bloom where notifications glow,
A little rabbit cheers—onward the app grows!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change—upgrading Expo from SDK 52 to 54—which is reflected throughout the changeset including dependency updates, version bumps, and compatibility adjustments.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The change from SplashScreen.preventAutoHideAsync()/hideAsync() to a bare SplashScreen.hide() removes the explicit control over when the splash screen disappears; consider either using the new recommended expo-router splash handling or keeping an explicit preventAutoHideAsync + corresponding hide call to avoid timing regressions on slower devices.
  • In FilterRow, dropping the Dropdown<OptionItem> generics reduces type safety on the dropdown props; consider keeping the generic parameter (or adding an explicit prop type) so that option shape and value/onChange remain strongly typed.
  • The ExternalLink component now constrains href to ExternalPathString, which may be too strict for arbitrary external URLs opened via openBrowserAsync; double-check that all existing external HTTP/HTTPS links still type-check and work as expected, or widen the href type if needed.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The change from `SplashScreen.preventAutoHideAsync()`/`hideAsync()` to a bare `SplashScreen.hide()` removes the explicit control over when the splash screen disappears; consider either using the new recommended expo-router splash handling or keeping an explicit `preventAutoHideAsync` + corresponding hide call to avoid timing regressions on slower devices.
- In `FilterRow`, dropping the `Dropdown<OptionItem>` generics reduces type safety on the dropdown props; consider keeping the generic parameter (or adding an explicit prop type) so that option shape and `value`/`onChange` remain strongly typed.
- The `ExternalLink` component now constrains `href` to `ExternalPathString`, which may be too strict for arbitrary external URLs opened via `openBrowserAsync`; double-check that all existing external HTTP/HTTPS links still type-check and work as expected, or widen the `href` type if needed.

## Individual Comments

### Comment 1
<location path="components/WorkoutCard.tsx" line_range="141" />
<code_context>
       const isMenuOpen = menuVisible === item.exercise_id;

       return (
-        <Sortable.Pressable style={[styles.exerciseItem]}>
+        <View style={[styles.exerciseItem]}>
           <TouchableOpacity
             onPress={() =>
</code_context>
<issue_to_address>
**issue (bug_risk):** Replacing `Sortable.Pressable` with a plain `View` may break drag-and-drop behavior for exercises.

`Sortable.Pressable` is what connects touch gestures to the sortable list so items can be dragged and reordered. Replacing it with a plain `View` likely prevents the parent `Sortable` from receiving the expected gesture events, breaking or degrading drag/reorder. If you only need different press behavior, keep `Sortable.Pressable` and place `TouchableOpacity` inside it, or follow the sortable library’s recommended item wrapper for RN 0.81.
</issue_to_address>

### Comment 2
<location path="babel.config.js" line_range="5" />
<code_context>
   api.cache(true);
   return {
     presets: ["babel-preset-expo"],
-    plugins: ["react-native-reanimated/plugin"],
+    plugins: ["react-native-worklets/plugin"],
   };
 };
</code_context>
<issue_to_address>
**issue (bug_risk):** Removing `react-native-reanimated/plugin` can break Reanimated worklets while still depending on `react-native-reanimated`.

Because `react-native-reanimated` is still a dependency (`~4.1.1`), its Babel plugin is needed so worklets and animations are correctly transformed. Replacing it with only `react-native-worklets/plugin` can cause runtime errors or no-op animations. Either keep both plugins in the order recommended by their docs, or only remove the Reanimated plugin once all Reanimated usage has been eliminated.
</issue_to_address>

### Comment 3
<location path="hooks/__tests__/useAnimatedImageQuery.test.ts" line_range="38-40" />
<code_context>
   //
   beforeAll(() => {
     jest.spyOn(global, "setTimeout").mockImplementation((callback: any) => {
-      // Immediately invoke the callback instead of waiting
       callback();
-      // Return a mock timer id
-      return 0 as unknown as NodeJS.Timeout;
+      return 0 as unknown as ReturnType<typeof setTimeout>;
     });
   });
</code_context>
<issue_to_address>
**issue (testing):** Restore the `setTimeout` spy after tests to avoid leaking the mock into other suites

Because the spy is created in `beforeAll` and never cleaned up, it affects all subsequent tests in this Jest environment and can hide timer-related issues. Add an `afterAll`/`afterEach` to call `jest.restoreAllMocks()` or to restore this specific spy, or switch to `jest.useFakeTimers()` with `jest.runAllTimers()` for isolated, idiomatic timer control.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread components/WorkoutCard.tsx
Comment thread babel.config.js
Comment thread hooks/__tests__/useAnimatedImageQuery.test.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
hooks/useAnimatedImageQuery.ts (1)

71-78: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

cacheTime is silenced by the type cast but silently ignored at runtime in TanStack Query v5 — the 24-hour GC window is never applied.

TanStack Query v5 renamed cacheTime to gcTime to better reflect its purpose. cacheTime must be replaced with gcTime in all query options. The as UseQueryOptions<string, Error> cast on line 78 suppresses the TypeScript error, so the misconfiguration is invisible at compile time. At runtime the option is silently ignored, and the default 5-minute GC time will be used instead of the intended 24 hours — meaning cached animated images will be evicted far sooner than expected, causing redundant re-downloads.

🐛 Proposed fix
  return useQuery<string, Error>({
    queryKey: ["animatedImage", animatedUrlPath],
    queryFn: () =>
      fetchAnimatedImageUrl(exerciseId, animatedUrlPath, localPath),
    enabled: !!animatedUrlPath,
    staleTime: Infinity,
-   cacheTime: 1000 * 60 * 60 * 24, // 24 hours
+   gcTime: 1000 * 60 * 60 * 24, // 24 hours
  } as UseQueryOptions<string, Error>);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/useAnimatedImageQuery.ts` around lines 71 - 78, The query option uses
the old cacheTime (silenced by the "as UseQueryOptions<string, Error>" cast) so
TanStack Query v5 ignores the 24-hour setting; update the options in
useAnimatedImageQuery to replace cacheTime with gcTime: 1000 * 60 * 60 * 24 and
remove the forced type cast so TypeScript surfaces any mismatches. Locate
useAnimatedImageQuery (and its useQuery call that invokes fetchAnimatedImageUrl)
and change the option key from cacheTime to gcTime and drop the "as
UseQueryOptions<string, Error>" cast (or replace it with a correctly-typed
generic) so the gcTime is applied at runtime.
hooks/__tests__/useAnimatedImageQuery.test.ts (1)

135-142: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update cacheTime to gcTime in both test and implementation – breaking change in TanStack Query v5.

In TanStack Query v5, cacheTime was renamed to gcTime. The project uses @tanstack/react-query v5.51.21, so both the implementation (hooks/useAnimatedImageQuery.ts line 77) and the test assertion (hooks/__tests__/useAnimatedImageQuery.test.ts line 140) must be updated.

Fixes required
  1. In hooks/useAnimatedImageQuery.ts line 77:
-    cacheTime: 1000 * 60 * 60 * 24, // 24 hours
+    gcTime: 1000 * 60 * 60 * 24, // 24 hours
  1. In hooks/__tests__/useAnimatedImageQuery.test.ts line 140:
-        cacheTime: 1000 * 60 * 60 * 24, // 24 hours
+        gcTime: 1000 * 60 * 60 * 24, // 24 hours
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/__tests__/useAnimatedImageQuery.test.ts` around lines 135 - 142, The
tests and implementation use the TanStack Query option name cacheTime which was
renamed to gcTime in v5; update the call sites that configure the query options
for useAnimatedImageQuery and the test's mock assertion: in the implementation
where the query options object is built for useAnimatedImageQuery (reference the
queryKey ["animatedImage", animatedUrlPath] and the queryFn provided) replace
cacheTime with gcTime, and in the test update the mockUseQuery expectation to
expect gcTime: 1000 * 60 * 60 * 24 instead of cacheTime so the mock matches the
new option name.
components/WorkoutCard.tsx (1)

141-208: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace <View> wrapper with Sortable.Touchable to restore drag gesture handling.

The removal of Sortable.Pressable has broken drag-to-reorder functionality. The react-native-sortables documentation explicitly warns that using React Native's standard Pressable or TouchableOpacity directly inside sortable items interferes with drag detection. The library provides Sortable.Touchable (or Sortable.Pressable) precisely to coordinate press and drag gestures safely.

The current implementation wraps the exercise item in a bare <View> containing a <TouchableOpacity>, which reintroduces the gesture conflict that the library component prevents. Restore the library's gesture-aware component to fix reordering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/WorkoutCard.tsx` around lines 141 - 208, The drag-to-reorder broke
because the exercise item is wrapped in a plain <View> and uses React Native's
<TouchableOpacity>, which conflicts with react-native-sortables; replace the
outer <View> (and/or the inner <TouchableOpacity>) for the item in WorkoutCard
with the library's gesture-aware component (Sortable.Touchable or
Sortable.Pressable) so drag detection is coordinated; ensure you update the JSX
around the item container and keep existing props/handlers (onPress/router.push,
style, children, Menu anchor callbacks like
openMenu/closeMenu/removeExercise/handleReplace) and add the Sortable import
from react-native-sortables if not already present.
🧹 Nitpick comments (2)
utils/notificationSetup.ts (1)

7-13: ⚡ Quick win

Remove the deprecated shouldShowAlert field.

shouldShowAlert has been replaced by shouldShowBanner and shouldShowList in the NotificationBehavior interface. The current Expo documentation no longer includes shouldShowAlert in handler examples, and a deprecation warning is emitted at runtime when shouldShowAlert is still present.

shouldShowBanner and shouldShowList have been correctly added, but the now-redundant shouldShowAlert: true should be removed to complete the migration.

♻️ Proposed fix
   handleNotification: async () => ({
-    shouldShowAlert: true,
     shouldPlaySound: false,
     shouldSetBadge: false,
     shouldShowBanner: true,
     shouldShowList: true,
   }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/notificationSetup.ts` around lines 7 - 13, Remove the deprecated
shouldShowAlert property from the handleNotification returned object in
utils/notificationSetup.ts; edit the handleNotification async handler (the
handleNotification arrow function) to return only shouldPlaySound,
shouldSetBadge, shouldShowBanner, and shouldShowList so the object conforms to
the current NotificationBehavior interface and avoids the deprecation warning at
runtime.
app/(app)/custom-exercise.tsx (1)

147-150: ⚡ Quick win

MediaTypeOptions.Images is deprecated in expo-image-picker v17 – migrate to the string array form.

MediaTypeOptions is deprecated in v17; the replacement is passing an array of MediaType strings instead.

♻️ Proposed fix
-  let result = await ImagePicker.launchImageLibraryAsync({
-    mediaTypes: ImagePicker.MediaTypeOptions.Images,
+  let result = await ImagePicker.launchImageLibraryAsync({
+    mediaTypes: ['images'],
     allowsEditing: true,
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(app)/custom-exercise.tsx around lines 147 - 150, Replace the deprecated
MediaTypeOptions usage in the ImagePicker call: in the
ImagePicker.launchImageLibraryAsync invocation, change the mediaTypes option
from ImagePicker.MediaTypeOptions.Images to the new string-array form, e.g.
mediaTypes: ['Images'] (or ['Images','Videos'] if you need both); keep other
options like allowsEditing unchanged and remove any reliance on MediaTypeOptions
elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/_layout.tsx`:
- Around line 130-135: Call SplashScreen.preventAutoHideAsync() at module scope
(before any React components render) to stop the native splash from auto-hiding;
add a top-level invocation (and optionally catch/log any rejection) near the top
of app/_layout.tsx so the existing useEffect that calls SplashScreen.hide() (and
the setupNotificationChannel() logic) can reliably control when the splash is
dismissed.

In `@babel.config.js`:
- Line 5: Remove the explicit "react-native-worklets/plugin" entry from the
plugins array in babel.config.js: locate the plugins property and delete the
"react-native-worklets/plugin" string so that babel-preset-expo (used by the
project) is the sole provider of the worklets plugin; ensure the plugins array
remains valid (comma-separated entries and trailing commas adjusted) and do not
add any additional worklets plugin elsewhere.

---

Outside diff comments:
In `@components/WorkoutCard.tsx`:
- Around line 141-208: The drag-to-reorder broke because the exercise item is
wrapped in a plain <View> and uses React Native's <TouchableOpacity>, which
conflicts with react-native-sortables; replace the outer <View> (and/or the
inner <TouchableOpacity>) for the item in WorkoutCard with the library's
gesture-aware component (Sortable.Touchable or Sortable.Pressable) so drag
detection is coordinated; ensure you update the JSX around the item container
and keep existing props/handlers (onPress/router.push, style, children, Menu
anchor callbacks like openMenu/closeMenu/removeExercise/handleReplace) and add
the Sortable import from react-native-sortables if not already present.

In `@hooks/__tests__/useAnimatedImageQuery.test.ts`:
- Around line 135-142: The tests and implementation use the TanStack Query
option name cacheTime which was renamed to gcTime in v5; update the call sites
that configure the query options for useAnimatedImageQuery and the test's mock
assertion: in the implementation where the query options object is built for
useAnimatedImageQuery (reference the queryKey ["animatedImage", animatedUrlPath]
and the queryFn provided) replace cacheTime with gcTime, and in the test update
the mockUseQuery expectation to expect gcTime: 1000 * 60 * 60 * 24 instead of
cacheTime so the mock matches the new option name.

In `@hooks/useAnimatedImageQuery.ts`:
- Around line 71-78: The query option uses the old cacheTime (silenced by the
"as UseQueryOptions<string, Error>" cast) so TanStack Query v5 ignores the
24-hour setting; update the options in useAnimatedImageQuery to replace
cacheTime with gcTime: 1000 * 60 * 60 * 24 and remove the forced type cast so
TypeScript surfaces any mismatches. Locate useAnimatedImageQuery (and its
useQuery call that invokes fetchAnimatedImageUrl) and change the option key from
cacheTime to gcTime and drop the "as UseQueryOptions<string, Error>" cast (or
replace it with a correctly-typed generic) so the gcTime is applied at runtime.

---

Nitpick comments:
In `@app/`(app)/custom-exercise.tsx:
- Around line 147-150: Replace the deprecated MediaTypeOptions usage in the
ImagePicker call: in the ImagePicker.launchImageLibraryAsync invocation, change
the mediaTypes option from ImagePicker.MediaTypeOptions.Images to the new
string-array form, e.g. mediaTypes: ['Images'] (or ['Images','Videos'] if you
need both); keep other options like allowsEditing unchanged and remove any
reliance on MediaTypeOptions elsewhere.

In `@utils/notificationSetup.ts`:
- Around line 7-13: Remove the deprecated shouldShowAlert property from the
handleNotification returned object in utils/notificationSetup.ts; edit the
handleNotification async handler (the handleNotification arrow function) to
return only shouldPlaySound, shouldSetBadge, shouldShowBanner, and
shouldShowList so the object conforms to the current NotificationBehavior
interface and avoids the deprecation warning at runtime.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0b2c98c5-1c28-4e79-b903-89aa288e1312

📥 Commits

Reviewing files that changed from the base of the PR and between 5a6dd16 and 5e1b644.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • .eslintrc.js
  • app.config.js
  • app/(app)/(create-plan)/create.tsx
  • app/(app)/(tabs)/(plans)/overview.tsx
  • app/(app)/(tabs)/(stats)/history-details.tsx
  • app/(app)/custom-exercise.tsx
  • app/_layout.tsx
  • babel.config.js
  • components/ExternalLink.tsx
  • components/FilterRow.tsx
  • components/Onboarding.tsx
  • components/WorkoutCard.tsx
  • hooks/__tests__/useAnimatedImageQuery.test.ts
  • hooks/useAnimatedImageQuery.ts
  • package.json
  • tsconfig.json
  • utils/backup.ts
  • utils/clearUserData.ts
  • utils/downloadAllAnimatedImages.ts
  • utils/initAppDataDB.ts
  • utils/notificationSetup.ts

Comment thread app/_layout.tsx
Comment thread babel.config.js Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
jestSetupFile.js (2)

56-61: 💤 Low value

Guard against non-string path in getInfoAsync mock.

path.includes("appData2.db") will throw TypeError if any caller passes a non-string (e.g., a URL instance or undefined while a test exercises an unhappy path). The production getInfoAsync accepts strings only in legacy, so it's almost certainly fine — but a defensive String(path).includes(...) removes a footgun for future tests. Optional.

Also applies to: 74-79

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jestSetupFile.js` around lines 56 - 61, The mock getInfoAsync uses
path.includes("appData2.db") which will throw if path isn't a string; change the
check to coerce or guard the input—e.g., use
String(path).includes("appData2.db") or typeof path === "string" &&
path.includes("appData2.db")—so getInfoAsync returns exists safely for
non-string inputs; apply the same fix to the duplicate mock block around the
getInfoAsync usage at lines 74-79.

53-88: 💤 Low value

Consider deduplicating the two expo-file-system mocks.

The expo-file-system and expo-file-system/legacy mocks are byte-identical. Extracting a shared factory keeps them from drifting if one is later tweaked (e.g., a test customises getInfoAsync for the legacy path only). Optional — current duplication is harmless.

♻️ Suggested refactor
-jest.mock("expo-file-system", () => ({
-  documentDirectory: "/mock/document/directory/",
-  getInfoAsync: jest.fn((path) =>
-    Promise.resolve({
-      exists: path.includes("appData2.db"),
-      isDirectory: false,
-    }),
-  ),
-  createDownloadResumable: jest.fn(() => ({
-    downloadAsync: jest.fn(() =>
-      Promise.resolve({ uri: "/default/mock/path.webp" }),
-    ),
-  })),
-  deleteAsync: jest.fn().mockResolvedValue(undefined),
-  makeDirectoryAsync: jest.fn().mockResolvedValue(undefined),
-  copyAsync: jest.fn().mockResolvedValue(undefined),
-}));
-
-jest.mock("expo-file-system/legacy", () => ({
-  documentDirectory: "/mock/document/directory/",
-  getInfoAsync: jest.fn((path) =>
-    Promise.resolve({
-      exists: path.includes("appData2.db"),
-      isDirectory: false,
-    }),
-  ),
-  createDownloadResumable: jest.fn(() => ({
-    downloadAsync: jest.fn(() =>
-      Promise.resolve({ uri: "/default/mock/path.webp" }),
-    ),
-  })),
-  deleteAsync: jest.fn().mockResolvedValue(undefined),
-  makeDirectoryAsync: jest.fn().mockResolvedValue(undefined),
-  copyAsync: jest.fn().mockResolvedValue(undefined),
-}));
+const buildFileSystemMock = () => ({
+  documentDirectory: "/mock/document/directory/",
+  getInfoAsync: jest.fn((path) =>
+    Promise.resolve({
+      exists: path.includes("appData2.db"),
+      isDirectory: false,
+    }),
+  ),
+  createDownloadResumable: jest.fn(() => ({
+    downloadAsync: jest.fn(() =>
+      Promise.resolve({ uri: "/default/mock/path.webp" }),
+    ),
+  })),
+  deleteAsync: jest.fn().mockResolvedValue(undefined),
+  makeDirectoryAsync: jest.fn().mockResolvedValue(undefined),
+  copyAsync: jest.fn().mockResolvedValue(undefined),
+});
+
+jest.mock("expo-file-system", () => buildFileSystemMock());
+jest.mock("expo-file-system/legacy", () => buildFileSystemMock());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jestSetupFile.js` around lines 53 - 88, The two jest.mock blocks for
"expo-file-system" and "expo-file-system/legacy" are identical; extract a shared
mock factory function (e.g., createExpoFileSystemMock) that returns the object
with documentDirectory, getInfoAsync, createDownloadResumable, deleteAsync,
makeDirectoryAsync, and copyAsync, then call jest.mock("expo-file-system", () =>
createExpoFileSystemMock()) and jest.mock("expo-file-system/legacy", () =>
createExpoFileSystemMock()) so both mocks are maintained in one place and avoid
drifting when modifying getInfoAsync or other methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@jestSetupFile.js`:
- Around line 56-61: The mock getInfoAsync uses path.includes("appData2.db")
which will throw if path isn't a string; change the check to coerce or guard the
input—e.g., use String(path).includes("appData2.db") or typeof path === "string"
&& path.includes("appData2.db")—so getInfoAsync returns exists safely for
non-string inputs; apply the same fix to the duplicate mock block around the
getInfoAsync usage at lines 74-79.
- Around line 53-88: The two jest.mock blocks for "expo-file-system" and
"expo-file-system/legacy" are identical; extract a shared mock factory function
(e.g., createExpoFileSystemMock) that returns the object with documentDirectory,
getInfoAsync, createDownloadResumable, deleteAsync, makeDirectoryAsync, and
copyAsync, then call jest.mock("expo-file-system", () =>
createExpoFileSystemMock()) and jest.mock("expo-file-system/legacy", () =>
createExpoFileSystemMock()) so both mocks are maintained in one place and avoid
drifting when modifying getInfoAsync or other methods.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1b7f0d8b-3bb8-4855-b22d-0f033793c0c1

📥 Commits

Reviewing files that changed from the base of the PR and between 5e1b644 and 4d2e5f1.

📒 Files selected for processing (11)
  • app/(app)/custom-exercise.tsx
  • app/_layout.tsx
  • babel.config.js
  • components/WorkoutCard.tsx
  • hooks/__tests__/useAnimatedImageQuery.test.ts
  • hooks/useAnimatedImageQuery.ts
  • jestSetupFile.js
  • utils/__tests__/backup.test.ts
  • utils/__tests__/downloadAllAnimatedImages.test.ts
  • utils/__tests__/initAppDataDB.test.ts
  • utils/notificationSetup.ts
💤 Files with no reviewable changes (1)
  • babel.config.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • utils/notificationSetup.ts

@isotronic isotronic merged commit 22e8212 into master May 6, 2026
3 checks passed
@isotronic isotronic deleted the expo-upgrade branch May 6, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant