Skip to content

Commit af8f179

Browse files
committed
Merge branch 'main' into issue-84757
2 parents ecc0cfe + 0f18131 commit af8f179

230 files changed

Lines changed: 5120 additions & 2681 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ android {
111111
minSdkVersion rootProject.ext.minSdkVersion
112112
targetSdkVersion rootProject.ext.targetSdkVersion
113113
multiDexEnabled rootProject.ext.multiDexEnabled
114-
versionCode 1009033605
115-
versionName "9.3.36-5"
114+
versionCode 1009033700
115+
versionName "9.3.37-0"
116116
// Supported language variants must be declared here to avoid from being removed during the compilation.
117117
// This also helps us to not include unnecessary language variants in the APK.
118118
resConfigs "en", "es"

babel.config.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const webpack = {
6363
};
6464

6565
const metro = {
66-
presets: [[require('@react-native/babel-preset'), {disableImportExportTransform: true}]],
66+
presets: [require('@react-native/babel-preset')],
6767
plugins: [
6868
['babel-plugin-react-compiler', ReactCompilerConfig], // must run first!
6969

@@ -174,14 +174,5 @@ module.exports = (api) => {
174174
const runningIn = api.caller((args = {}) => args.name);
175175
console.debug(' - running in: ', runningIn);
176176

177-
// Jest runs in Node.js without Metro's experimentalImportSupport transform,
178-
// so Babel must handle import/export transforms for tests.
179-
if (runningIn === 'babel-jest') {
180-
return {
181-
...metro,
182-
presets: [[require('@react-native/babel-preset'), {disableImportExportTransform: false}]],
183-
};
184-
}
185-
186-
return runningIn === 'metro' ? metro : webpack;
177+
return ['metro', 'babel-jest'].includes(runningIn) ? metro : webpack;
187178
};

contributingGuides/AI_ETIQUETTE.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# AI Etiquette
2+
3+
AI is a great tool for drafting, searching, summarizing, and exploring. Humans must own judgment, accountability, and quality.
4+
5+
---
6+
7+
## You Own What You Post
8+
9+
If you submit a message, pull request, comment, or code with AI assistance, you are fully accountable for it. Before posting anything AI helped produce, you should be able to explain it without AI's help and feel confident putting your name behind it.
10+
11+
## Avoid AI Slop
12+
13+
"AI slop" is AI generated content copied and pasted with minimal to no human review. Use AI for brainstorming, research, and drafting, but treat it as a starting point, not the final result. Add your own context and nuance before sharing.
14+
15+
## Don't Trust AI Blindly
16+
17+
AI confidently says things that sound correct but are wrong. Always verify claims against source material. It's your job to understand and validate any AI-generated information you act on or share.
18+
19+
## When AI Isn't Enough
20+
21+
If a task can be fully automated with no human review, it should be automated for everyone, not done manually with AI. Our [AI Reviewers](/contributingGuides/philosophies/AI-REVIEWER.md) are an example: they do a first pass on code review, but they don't replace human review. You should still apply your own judgment when reviewing code, writing updates, or making decisions.
22+
23+
---
24+
25+
## Best Practices
26+
27+
### Don't
28+
29+
- Paste AI output directly without adding your own value
30+
- Submit AI-generated code you haven't tested, debugged, and confirmed follows our [coding standards](./STYLE.md)
31+
- Submit AI-generated code you don't fully understand
32+
- Post poorly formatted AI output or long walls of text without distilling the key points
33+
- Blame AI for mistakes. The mistake is yours regardless of the source
34+
- Use AI to generate responses during live conversations instead of engaging directly
35+
36+
### Do
37+
38+
- Use AI to summarize, draft, research, and explore
39+
- Review and edit all AI output before sharing. Put it in your own voice and make sure it's accurate
40+
- Verify AI claims against source information
41+
- Review all AI-generated code and content before asking others to review it
42+
- Verify AI-written PR descriptions and add context needed to explain the change
43+
- Provide evidence of manual testing on PRs, especially when test steps were AI-generated
44+
- Invest time in learning to write effective prompts and recognize quality output

contributingGuides/CONTRIBUTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ This helps future investigators understand the history and current status of err
211211
### Important note about JavaScript Style
212212
- Read our official [JavaScript and React style guide](https://github.com/Expensify/App/blob/main/contributingGuides/STYLE.md). Please refer to our Style Guide before asking for a review.
213213
214+
### Using AI tools
215+
- If you use AI tools (Copilot, Cursor, ChatGPT, etc.) to help write code or PR descriptions, please read our [AI Etiquette guide](https://github.com/Expensify/App/blob/main/contributingGuides/AI_ETIQUETTE.md). You are accountable for all AI output you submit.
216+
214217
### For external agencies that Expensify partners with
215218
Follow all the above above steps and processes. When you find a job you'd like to work on:
216219
- Post “I’m from [agency], I’d like to work on this job”

contributingGuides/NAVIGATION.md

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ The navigation in the app is built on top of the `react-navigation` library. To
2626
- [Dynamic routes configuration](#dynamic-routes-configuration)
2727
- [Entry screens (access control)](#entry-screens-access-control)
2828
- [Current limitations (work in progress)](#current-limitations-work-in-progress)
29+
- [Multi-segment dynamic routes](#multi-segment-dynamic-routes)
30+
- [Dynamic routes with query parameters](#dynamic-routes-with-query-parameters)
2931
- [How to add a new dynamic route](#how-to-add-a-new-dynamic-route)
3032
- [Migrating from backTo to dynamic routes](#migrating-from-backto-to-dynamic-routes)
3133
- [How to remove backTo from URL (Legacy)](#how-to-remove-backto-from-url)
@@ -700,7 +702,6 @@ A dynamic route is a URL suffix (e.g. `verify-account`) that can be appended to
700702
Do not use dynamic routes when:
701703
- Your use case falls under the [current limitations](#current-limitations-work-in-progress):
702704
- You need to stack multiple dynamic route suffixes (e.g. `/a/verify-account/another-flow`).
703-
- Your suffix includes path or query parameters (e.g. `verify-account/:id` or `verify-account?tab=details`).
704705
- The screen has a single, fixed entry and a fixed back destination. In this case, use a normal static route instead.
705706

706707
### Dynamic routes configuration
@@ -732,21 +733,119 @@ When adding or extending a dynamic route, list every screen that should be able
732733
### Current limitations (work in progress)
733734

734735
- **Stacking:** Multiple dynamic route suffixes on top of each other (e.g. `/a/verify-account/another-flow`) are not supported. Only one dynamic suffix per path is allowed.
735-
- **Suffix shape:** Suffixes must be a single path segment. Compound suffixes with extra path segments (e.g. `a/b`) are not supported.
736-
- **Parameters:** Suffixes must not include path params (e.g. `a/:reportID`) or query params (e.g. `a?foo=bar`). Use a single literal segment like `verify-account` only.
736+
- **Path parameters:** Suffixes must not include path params (e.g. `a/:reportID`). Query parameters are supported - see [Dynamic routes with query parameters](#dynamic-routes-with-query-parameters).
737737

738738
If you try to use dynamic routes for these cases now, you will either fail to navigate to the page at all or end up on a non-existent page, and the navigation will be broken.
739739

740-
### How to add a new dynamic route
740+
### Multi-segment dynamic routes
741+
742+
Dynamic route suffixes are not limited to a single path segment -
743+
they can span multiple segments separated by `/`.
744+
For example, the suffix `add-bank-account/verify-account` is a valid
745+
multi-segment suffix that combines two segments into one dynamic route.
746+
747+
When the URL is parsed, the matching algorithm
748+
iterates from the longest candidate suffix to the shortest,
749+
so overlapping registrations are resolved deterministically.
750+
For instance, if both `verify-account` and `add-bank-account/verify-account`
751+
are registered, a path ending with `/add-bank-account/verify-account`
752+
will always match the longer, more specific suffix.
753+
754+
### Dynamic routes with query parameters
755+
756+
Dynamic route suffixes can carry query parameters
757+
(e.g. `country?country=US`).
758+
This is useful when a dynamic screen needs initial data passed
759+
through the URL - for example, a country selector that
760+
pre-selects the current country.
761+
762+
#### How to add query parameters to a dynamic route
763+
764+
1. In `DYNAMIC_ROUTES` (in [`src/ROUTES.ts`](../src/ROUTES.ts)), add a `getRoute` function
765+
that returns the suffix with query parameters and a `queryParams`
766+
array listing every parameter key that the suffix may add:
767+
768+
```ts
769+
ADDRESS_COUNTRY: {
770+
path: 'country',
771+
entryScreens: [SCREENS.SETTINGS.PROFILE.ADDRESS, /* ... */],
772+
getRoute: (country = '') => `country${country ? `?country=${country}` : ''}`,
773+
queryParams: ['country'],
774+
},
775+
```
741776

742-
1. Add to `DYNAMIC_ROUTES` in `src/ROUTES.ts`: define `path` and `entryScreens` (screen names that may open this route).
743-
2. Add a screen constant in `src/SCREENS.ts`. The name must start with the `DYNAMIC_` prefix (e.g. `SETTINGS.DYNAMIC_VERIFY_ACCOUNT`) so dynamic screens can be distinguished from static ones.
744-
3. Register in linking config in `src/libs/Navigation/linkingConfig/config.ts`: map the new screen to `DYNAMIC_ROUTES.<KEY>.path`.
745-
4. Implement the page component: Use `createDynamicRoute(DYNAMIC_ROUTES.<KEY>.path)` to navigate to the flow and `useDynamicBackPath(DYNAMIC_ROUTES.<KEY>.path)` to get the back path. Pass these into your base UI (e.g. `VerifyAccountPageBase` with `navigateBackTo` / `navigateForwardTo`).
746-
5. Register the screen in the appropriate modal/stack navigator (e.g. `src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx`).
747-
6. Types: Add the screen to the navigator param list in `src/libs/Navigation/types.ts` (no params for the new screen).
777+
2. Navigate using `createDynamicRoute` with the output of `getRoute`:
778+
779+
```ts
780+
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.ADDRESS_COUNTRY.getRoute(countryCode)));
781+
// Produces e.g. /settings/profile/address/country?country=US
782+
```
783+
784+
3. In the page component, read query params from `route.params` as usual:
785+
786+
```ts
787+
const currentCountry = route.params?.country ?? '';
788+
```
789+
790+
> [!CAUTION]
791+
> **`queryParams` array is mandatory.**
792+
> When you define a `getRoute` function that adds query parameters
793+
> after `?` in a dynamic route, you **must** also define a `queryParams`
794+
> array containing **every** parameter key that `getRoute` may produce.
795+
> The `queryParams` array is used to strip suffix-specific parameters when navigating back.
796+
> Without it, query parameters will leak into the parent path
797+
> and break back navigation.
798+
799+
> [!CAUTION]
800+
> **Query parameter names must not collide with inherited entry screen parameters.**
801+
> Do not use a query parameter name in a dynamic suffix that
802+
> already exists as a parameter of any entry screen listed
803+
> in `entryScreens`.
804+
> For example, if an entry screen has a `country` query parameter,
805+
> do not add `country` as a query parameter in your dynamic route.
806+
> When both paths carry the same parameter key,
807+
> `createDynamicRoute` will throw an error:
808+
> `Query param "X" exists in both base path and dynamic suffix.`
809+
> `This is not allowed.`
810+
> This guard prevents non-deterministic parameter handling
811+
> and accidental overwriting of inherited parameters.
812+
813+
The Address Country flow is the reference implementation for
814+
dynamic routes with query parameters:
815+
see [`src/components/CountrySelector.tsx`](../src/components/CountrySelector.tsx) (navigation call)
816+
and [`src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx`](../src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx)
817+
(page component).
818+
819+
### How to add a new dynamic route
748820

749-
The Verify Account flow (WalletDynamic Verify Account) is the reference implementation: see `src/pages/settings/DynamicVerifyAccountPage.tsx` and the Wallet entry point in `src/pages/settings/Wallet/WalletPage/index.tsx`.
821+
1. Add to `DYNAMIC_ROUTES` in [`src/ROUTES.ts`](../src/ROUTES.ts): define `path` and
822+
`entryScreens` (screen names that may open this route).
823+
If the suffix needs query parameters, also define `getRoute`
824+
and `queryParams` - see
825+
[Dynamic routes with query parameters](#dynamic-routes-with-query-parameters).
826+
2. Add a screen constant in [`src/SCREENS.ts`](../src/SCREENS.ts).
827+
The name must start with the `DYNAMIC_` prefix
828+
(e.g. `SETTINGS.DYNAMIC_VERIFY_ACCOUNT`) so dynamic screens
829+
can be distinguished from static ones.
830+
3. Register in linking config in
831+
[`src/libs/Navigation/linkingConfig/config.ts`](../src/libs/Navigation/linkingConfig/config.ts):
832+
map the new screen to `DYNAMIC_ROUTES.<KEY>.path`.
833+
4. Implement the page component:
834+
Use `createDynamicRoute(DYNAMIC_ROUTES.<KEY>.path)` to navigate
835+
to the flow and `useDynamicBackPath(DYNAMIC_ROUTES.<KEY>.path)`
836+
to get the back path. Pass these into your base UI
837+
(e.g. `VerifyAccountPageBase` with `navigateBackTo` /
838+
`navigateForwardTo`).
839+
5. Register the screen in the appropriate modal/stack navigator
840+
(e.g. [`src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx`](../src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx)).
841+
6. Types: Add the screen to the navigator param list in
842+
[`src/libs/Navigation/types.ts`](../src/libs/Navigation/types.ts) (no params for the new screen).
843+
844+
The Verify Account flow (WalletDynamic Verify Account) is the
845+
reference implementation:
846+
see [`src/pages/settings/DynamicVerifyAccountPage.tsx`](../src/pages/settings/DynamicVerifyAccountPage.tsx) and the
847+
Wallet entry point in
848+
[`src/pages/settings/Wallet/WalletPage/index.tsx`](../src/pages/settings/Wallet/WalletPage/index.tsx).
750849

751850
### Migrating from backTo to dynamic routes
752851

ios/NewExpensify/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<key>CFBundlePackageType</key>
2424
<string>APPL</string>
2525
<key>CFBundleShortVersionString</key>
26-
<string>9.3.36</string>
26+
<string>9.3.37</string>
2727
<key>CFBundleSignature</key>
2828
<string>????</string>
2929
<key>CFBundleURLTypes</key>
@@ -44,7 +44,7 @@
4444
</dict>
4545
</array>
4646
<key>CFBundleVersion</key>
47-
<string>9.3.36.5</string>
47+
<string>9.3.37.0</string>
4848
<key>FullStory</key>
4949
<dict>
5050
<key>OrgId</key>

ios/NotificationServiceExtension/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.3.36</string>
14+
<string>9.3.37</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.3.36.5</string>
16+
<string>9.3.37.0</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionPointIdentifier</key>

ios/ShareViewController/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.3.36</string>
14+
<string>9.3.37</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.3.36.5</string>
16+
<string>9.3.37.0</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionAttributes</key>

metro.config.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ const config = {
3535
transformer: {
3636
getTransformOptions: async () => ({
3737
transform: {
38-
experimentalImportSupport: true,
3938
inlineRequires: true,
4039
},
4140
}),

0 commit comments

Comments
 (0)