Skip to content

Commit 589fb73

Browse files
committed
Merge branch 'main' into close-date-selection-header-message
2 parents 3c2a24d + e2e2e3b commit 589fb73

168 files changed

Lines changed: 1910 additions & 3171 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
@@ -114,8 +114,8 @@ android {
114114
minSdkVersion rootProject.ext.minSdkVersion
115115
targetSdkVersion rootProject.ext.targetSdkVersion
116116
multiDexEnabled rootProject.ext.multiDexEnabled
117-
versionCode 1009019000
118-
versionName "9.1.90-0"
117+
versionCode 1009019005
118+
versionName "9.1.90-5"
119119
// Supported language variants must be declared here to avoid from being removed during the compilation.
120120
// This also helps us to not include unnecessary language variants in the APK.
121121
resConfigs "en", "es"
Lines changed: 16 additions & 0 deletions
Loading

contributingGuides/review/RULES.md

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# ✅ AI Code Review Rules
2+
3+
These rules are used to conduct structured code reviews on pull request diffs.
4+
5+
Each rule includes:
6+
- A unique **Rule ID**
7+
- **Pass/Fail condition**
8+
- **Reasoning**: Technical explanation of why the rule is important
9+
- Examples of good and bad usage
10+
11+
Very important:
12+
- Make sure you include a separate comment for every rule violation
13+
- Every comment has to reference a **Rule ID** it violates
14+
15+
---
16+
17+
## Performance Rules
18+
19+
### [PERF-1] No spread in list item's renderItem
20+
- **Condition**: When passing data to components in renderItem functions, avoid using spread operators to extend objects. Instead, pass the base object and additional properties as separate props to prevent unnecessary object creation on each render.
21+
- **Reasoning**: `renderItem` functions execute for every visible list item on each render. Creating new objects with spread operators forces React to treat each item as changed, preventing reconciliation optimizations and causing unnecessary re-renders of child components.
22+
23+
Good:
24+
```tsx
25+
<Component
26+
item={item}
27+
isSelected={isSelected}
28+
shouldAnimateInHighlight={isItemHighlighted}
29+
/>
30+
```
31+
32+
Bad:
33+
```tsx
34+
<Component
35+
item={{
36+
shouldAnimateInHighlight: isItemHighlighted,
37+
isSelected: selected,
38+
...item,
39+
}}
40+
/>
41+
```
42+
43+
---
44+
45+
### [PERF-2] Use early returns in array iteration methods
46+
- **Condition**: When using `.every()`, `.some()`, or similar methods, perform simple checks first with early returns before expensive operations.
47+
- **Reasoning**: Expensive operations can be any long-running synchronous tasks (like complex calculations) and should be avoided when simple property checks can eliminate items early. This reduces unnecessary computation and improves iteration performance, especially on large datasets.
48+
49+
Good:
50+
```ts
51+
const areAllTransactionsValid = transactions.every((transaction) => {
52+
if (!transaction.rawData || transaction.amount <= 0) {
53+
return false;
54+
}
55+
const validation = validateTransaction(transaction);
56+
return validation.isValid;
57+
});
58+
```
59+
60+
Bad:
61+
```ts
62+
const areAllTransactionsValid = transactions.every((transaction) => {
63+
const validation = validateTransaction(transaction);
64+
return validation.isValid;
65+
});
66+
```
67+
68+
---
69+
70+
### [PERF-3] Use OnyxListItemProvider hooks instead of useOnyx in renderItem
71+
- **Condition**: Components rendered inside `renderItem` functions should use dedicated hooks from `OnyxListItemProvider` instead of individual `useOnyx` calls.
72+
- **Reasoning**: Individual `useOnyx` calls in renderItem create separate subscriptions for each list item, causing memory overhead and update cascades. `OnyxListItemProvider` hooks provide optimized data access patterns specifically designed for list rendering performance.
73+
74+
Good:
75+
```tsx
76+
const personalDetails = usePersonalDetails();
77+
```
78+
79+
Bad:
80+
```tsx
81+
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
82+
```
83+
84+
---
85+
86+
### [PERF-4] Memoize objects and functions passed as props
87+
- **Condition**: Objects and functions passed as props should be properly memoized or simplified to primitive values to prevent unnecessary re-renders.
88+
- **Reasoning**: React uses referential equality to determine if props changed. New object/function instances on every render trigger unnecessary re-renders of child components, even when the actual data hasn't changed. Memoization preserves referential stability.
89+
90+
Good:
91+
```tsx
92+
const reportData = useMemo(() => ({
93+
reportID: report.reportID,
94+
type: report.type,
95+
isPinned: report.isPinned,
96+
}), [report.reportID, report.type, report.isPinned]);
97+
98+
return <ReportActionItem report={reportData} />
99+
```
100+
101+
Bad:
102+
```tsx
103+
const [report] = useOnyx(`ONYXKEYS.COLLECTION.REPORT${iouReport.id}`);
104+
105+
return <ReportActionItem report={report} />
106+
```
107+
108+
---
109+
110+
### [PERF-5] Use shallow comparisons instead of deep comparisons
111+
- **Condition**: In `React.memo` and similar optimization functions, compare only specific relevant properties instead of using deep equality checks.
112+
- **Reasoning**: Deep equality checks recursively compare all nested properties, creating performance overhead that often exceeds the re-render cost they aim to prevent. Shallow comparisons of specific relevant properties provide the same optimization benefits with minimal computational cost.
113+
114+
Good:
115+
```tsx
116+
memo(ReportActionItem, (prevProps, nextProps) =>
117+
prevProps.report.type === nextProps.report.type &&
118+
prevProps.report.reportID === nextProps.report.reportID &&
119+
prevProps.isSelected === nextProps.isSelected
120+
)
121+
```
122+
123+
Bad:
124+
```tsx
125+
memo(ReportActionItem, (prevProps, nextProps) =>
126+
deepEqual(prevProps.report, nextProps.report) &&
127+
prevProps.isSelected === nextProps.isSelected
128+
)
129+
```
130+
131+
---
132+
133+
### [PERF-6] Use specific properties as hook dependencies
134+
- **Condition**: In `useEffect`, `useMemo`, and `useCallback`, specify individual object properties as dependencies instead of passing entire objects.
135+
- **Reasoning**: Passing entire objects as dependencies causes hooks to re-execute whenever any property changes, even unrelated ones. Specifying individual properties creates more granular dependency tracking, reducing unnecessary hook executions and improving performance predictability.
136+
137+
Good:
138+
```tsx
139+
const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => {
140+
return {
141+
amountColumnSize: transactionItem.isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
142+
taxAmountColumnSize: transactionItem.isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
143+
dateColumnSize: transactionItem.shouldShowYear ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
144+
};
145+
}, [transactionItem.isAmountColumnWide, transactionItem.isTaxAmountColumnWide, transactionItem.shouldShowYear]);
146+
```
147+
148+
Bad:
149+
```tsx
150+
const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => {
151+
return {
152+
amountColumnSize: transactionItem.isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
153+
taxAmountColumnSize: transactionItem.isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
154+
dateColumnSize: transactionItem.shouldShowYear ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL,
155+
};
156+
}, [transactionItem]);
157+
```
158+
159+
---

ios/NewExpensify/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
</dict>
4545
</array>
4646
<key>CFBundleVersion</key>
47-
<string>9.1.90.0</string>
47+
<string>9.1.90.5</string>
4848
<key>FullStory</key>
4949
<dict>
5050
<key>OrgId</key>

ios/NotificationServiceExtension/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<key>CFBundleShortVersionString</key>
1414
<string>9.1.90</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.1.90.0</string>
16+
<string>9.1.90.5</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionPointIdentifier</key>

ios/ShareViewController/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<key>CFBundleShortVersionString</key>
1414
<string>9.1.90</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.1.90.0</string>
16+
<string>9.1.90.5</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionAttributes</key>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "new.expensify",
3-
"version": "9.1.90-0",
3+
"version": "9.1.90-5",
44
"author": "Expensify, Inc.",
55
"homepage": "https://new.expensify.com",
66
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
@@ -46,7 +46,7 @@
4646
"test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand",
4747
"perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure",
4848
"typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc",
49-
"lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=310 --cache --cache-location=node_modules/.cache/eslint",
49+
"lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=297 --cache --cache-location=node_modules/.cache/eslint",
5050
"lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh",
5151
"lint-watch": "npx eslint-watch --watch --changed",
5252
"shellcheck": "./scripts/shellCheck.sh",

src/CONST/index.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,7 @@ const CONST = {
677677
EUR_BILLING: 'eurBilling',
678678
MANUAL_DISTANCE: 'manualDistance',
679679
VACATION_DELEGATE: 'vacationDelegate',
680+
UBER_FOR_BUSINESS: 'uberForBusiness',
680681
},
681682
BUTTON_STATES: {
682683
DEFAULT: 'default',
@@ -1527,9 +1528,6 @@ const CONST = {
15271528
SEARCH_OPTION_LIST_DEBOUNCE_TIME: 300,
15281529
RESIZE_DEBOUNCE_TIME: 100,
15291530
UNREAD_UPDATE_DEBOUNCE_TIME: 300,
1530-
SEARCH_CONVERT_SEARCH_VALUES: 'search_convert_search_values',
1531-
SEARCH_MAKE_TREE: 'search_make_tree',
1532-
SEARCH_BUILD_TREE: 'search_build_tree',
15331531
SEARCH_FILTER_OPTIONS: 'search_filter_options',
15341532
USE_DEBOUNCED_STATE_DELAY: 300,
15351533
LIST_SCROLLING_DEBOUNCE_TIME: 200,
@@ -1538,10 +1536,8 @@ const CONST = {
15381536
PLAY_SOUND_MESSAGE_DEBOUNCE_TIME: 500,
15391537
NOTIFY_NEW_ACTION_DELAY: 700,
15401538
SKELETON_ANIMATION_SPEED: 3,
1541-
SEARCH_OPTIONS_COMPARISON: 'search_options_comparison',
15421539
SEARCH_MOST_RECENT_OPTIONS: 'search_most_recent_options',
15431540
DEBOUNCE_HANDLE_SEARCH: 'debounce_handle_search',
1544-
FAST_SEARCH_TREE_CREATION: 'fast_search_tree_creation',
15451541
},
15461542
PRIORITY_MODE: {
15471543
GSD: 'gsd',
@@ -2877,12 +2873,6 @@ const CONST = {
28772873
REIMBURSEMENT_NO: 'reimburseNo', // None
28782874
REIMBURSEMENT_MANUAL: 'reimburseManual', // Indirect
28792875
},
2880-
CASH_EXPENSE_REIMBURSEMENT_CHOICES: {
2881-
REIMBURSABLE_DEFAULT: 'reimbursableDefault', // Reimbursable by default
2882-
NON_REIMBURSABLE_DEFAULT: 'nonReimbursableDefault', // Non-reimbursable by default
2883-
ALWAYS_REIMBURSABLE: 'alwaysReimbursable', // Always Reimbursable
2884-
ALWAYS_NON_REIMBURSABLE: 'alwaysNonReimbursable', // Always Non Reimbursable
2885-
},
28862876
ID_FAKE: '_FAKE_',
28872877
EMPTY: 'EMPTY',
28882878
SECONDARY_ACTIONS: {
@@ -2910,6 +2900,7 @@ const CONST = {
29102900
ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled',
29112901
ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled',
29122902
ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled',
2903+
ARE_RECEIPT_PARTNERS_ENABLED: 'areReceiptPartnersEnabled',
29132904
ARE_COMPANY_CARDS_ENABLED: 'areCompanyCardsEnabled',
29142905
ARE_EXPENSIFY_CARDS_ENABLED: 'areExpensifyCardsEnabled',
29152906
ARE_INVOICES_ENABLED: 'areInvoicesEnabled',
@@ -3127,7 +3118,6 @@ const CONST = {
31273118
CFPB_PREPAID: 'cfpb.gov/prepaid',
31283119
CFPB_COMPLAINT: 'cfpb.gov/complaint',
31293120
FDIC_PREPAID: 'fdic.gov/deposit/deposits/prepaid.html',
3130-
USE_EXPENSIFY_FEES: 'use.expensify.com/fees',
31313121
},
31323122

31333123
LAYOUT_WIDTH: {
@@ -3742,7 +3732,6 @@ const CONST = {
37423732
TAG: 'tag',
37433733
TAX_RATE: 'taxRate',
37443734
TAX_AMOUNT: 'taxAmount',
3745-
REIMBURSABLE: 'reimbursable',
37463735
REPORT: 'report',
37473736
},
37483737
FOOTER: {
@@ -6410,6 +6399,7 @@ const CONST = {
64106399
PAID: 'paid',
64116400
EXPORTED: 'exported',
64126401
POSTED: 'posted',
6402+
WITHDRAWN: 'withdrawn',
64136403
TITLE: 'title',
64146404
ASSIGNEE: 'assignee',
64156405
REIMBURSABLE: 'reimbursable',
@@ -6454,6 +6444,7 @@ const CONST = {
64546444
PAID: 'paid',
64556445
EXPORTED: 'exported',
64566446
POSTED: 'posted',
6447+
WITHDRAWN: 'withdrawn',
64576448
TITLE: 'title',
64586449
ASSIGNEE: 'assignee',
64596450
REIMBURSABLE: 'reimbursable',

0 commit comments

Comments
 (0)