feat: expo upgrade from SDK 52 to 54#129
Conversation
…ing major updates for expo, react, and related libraries
…r improved layout
Reviewer's GuideUpgrades 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
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughThis 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. ChangesUpgrade, FileSystem Migration & Routing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
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. Comment |
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The change from
SplashScreen.preventAutoHideAsync()/hideAsync()to a bareSplashScreen.hide()removes the explicit control over when the splash screen disappears; consider either using the new recommended expo-router splash handling or keeping an explicitpreventAutoHideAsync+ corresponding hide call to avoid timing regressions on slower devices. - In
FilterRow, dropping theDropdown<OptionItem>generics reduces type safety on the dropdown props; consider keeping the generic parameter (or adding an explicit prop type) so that option shape andvalue/onChangeremain strongly typed. - The
ExternalLinkcomponent now constrainshreftoExternalPathString, which may be too strict for arbitrary external URLs opened viaopenBrowserAsync; double-check that all existing external HTTP/HTTPS links still type-check and work as expected, or widen thehreftype 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
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
cacheTimeis 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
cacheTimetogcTimeto better reflect its purpose.cacheTimemust be replaced withgcTimein all query options. Theas 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 winUpdate
cacheTimetogcTimein both test and implementation – breaking change in TanStack Query v5.In TanStack Query v5,
cacheTimewas renamed togcTime. The project uses@tanstack/react-queryv5.51.21, so both the implementation (hooks/useAnimatedImageQuery.tsline 77) and the test assertion (hooks/__tests__/useAnimatedImageQuery.test.tsline 140) must be updated.Fixes required
- In
hooks/useAnimatedImageQuery.tsline 77:- cacheTime: 1000 * 60 * 60 * 24, // 24 hours + gcTime: 1000 * 60 * 60 * 24, // 24 hours
- In
hooks/__tests__/useAnimatedImageQuery.test.tsline 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 winReplace
<View>wrapper withSortable.Touchableto restore drag gesture handling.The removal of
Sortable.Pressablehas broken drag-to-reorder functionality. The react-native-sortables documentation explicitly warns that using React Native's standardPressableorTouchableOpacitydirectly inside sortable items interferes with drag detection. The library providesSortable.Touchable(orSortable.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 winRemove the deprecated
shouldShowAlertfield.
shouldShowAlerthas been replaced byshouldShowBannerandshouldShowListin theNotificationBehaviorinterface. The current Expo documentation no longer includesshouldShowAlertin handler examples, and a deprecation warning is emitted at runtime whenshouldShowAlertis still present.
shouldShowBannerandshouldShowListhave been correctly added, but the now-redundantshouldShowAlert: trueshould 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.Imagesis deprecated inexpo-image-pickerv17 – migrate to the string array form.
MediaTypeOptionsis deprecated in v17; the replacement is passing an array ofMediaTypestrings 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (21)
.eslintrc.jsapp.config.jsapp/(app)/(create-plan)/create.tsxapp/(app)/(tabs)/(plans)/overview.tsxapp/(app)/(tabs)/(stats)/history-details.tsxapp/(app)/custom-exercise.tsxapp/_layout.tsxbabel.config.jscomponents/ExternalLink.tsxcomponents/FilterRow.tsxcomponents/Onboarding.tsxcomponents/WorkoutCard.tsxhooks/__tests__/useAnimatedImageQuery.test.tshooks/useAnimatedImageQuery.tspackage.jsontsconfig.jsonutils/backup.tsutils/clearUserData.tsutils/downloadAllAnimatedImages.tsutils/initAppDataDB.tsutils/notificationSetup.ts
…ime in useAnimatedImageQuery
There was a problem hiding this comment.
🧹 Nitpick comments (2)
jestSetupFile.js (2)
56-61: 💤 Low valueGuard against non-string
pathingetInfoAsyncmock.
path.includes("appData2.db")will throwTypeErrorif any caller passes a non-string (e.g., aURLinstance orundefinedwhile a test exercises an unhappy path). The productiongetInfoAsyncaccepts strings only in legacy, so it's almost certainly fine — but a defensiveString(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 valueConsider deduplicating the two
expo-file-systemmocks.The
expo-file-systemandexpo-file-system/legacymocks are byte-identical. Extracting a shared factory keeps them from drifting if one is later tweaked (e.g., a test customisesgetInfoAsyncfor 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
📒 Files selected for processing (11)
app/(app)/custom-exercise.tsxapp/_layout.tsxbabel.config.jscomponents/WorkoutCard.tsxhooks/__tests__/useAnimatedImageQuery.test.tshooks/useAnimatedImageQuery.tsjestSetupFile.jsutils/__tests__/backup.test.tsutils/__tests__/downloadAllAnimatedImages.test.tsutils/__tests__/initAppDataDB.test.tsutils/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
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:
Bug Fixes:
Enhancements:
Build:
Tests:
Summary by CodeRabbit
New Features
Bug Fixes / UX
Chores