Skip to content

Commit 6813329

Browse files
committed
Merge remote-tracking branch 'upstream/main' into 65896-magic-code-input
2 parents beb414a + e3aab61 commit 6813329

384 files changed

Lines changed: 10115 additions & 11680 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.

.github/workflows/presubmit.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: PR Presubmit Reviews
2+
3+
permissions:
4+
contents: read
5+
pull-requests: write
6+
7+
on:
8+
pull_request:
9+
types: [opened, synchronize, labeled]
10+
pull_request_review_comment:
11+
types: [created]
12+
13+
jobs:
14+
review:
15+
runs-on: ubuntu-latest
16+
if: contains(github.event.pull_request.labels.*.name, 'AI Review')
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Check required secrets
22+
run: |
23+
if [ -z "${{ secrets.LLM_API_KEY }}" ]; then
24+
echo "Error: LLM_API_KEY secret is not configured"
25+
exit 1
26+
fi
27+
28+
- name: Read RULES.md
29+
id: read-rules
30+
run: |
31+
RULES_CONTENT=$(cat "contributingGuides/review/RULES.md")
32+
{
33+
echo "content<<EOF"
34+
echo "$RULES_CONTENT"
35+
echo "EOF"
36+
} >> "$GITHUB_OUTPUT"
37+
38+
- name: Print RULES.md
39+
run: |
40+
echo "📋 RULES.md"
41+
echo "======================================"
42+
echo "${{ steps.read-rules.outputs.content }}"
43+
echo "======================================"
44+
45+
- uses: aldo-expensify/ai-reviewer-hackaton@c91b349b41efdf1eeaa290716628d76068148f0b
46+
env:
47+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48+
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
49+
LLM_MODEL: "claude-3-7-sonnet-20250219"
50+
with:
51+
style_guide_rules: ${{ steps.read-rules.outputs.content }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,6 @@ react-compiler-output.txt
153153

154154
# Generated by bob (for Nitro modules)
155155
modules/*/lib/
156+
157+
# Claude Code files
158+
.claude/

.storybook/preview.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import HTMLEngineProvider from '@src/components/HTMLEngineProvider';
1010
import {LocaleContextProvider} from '@src/components/LocaleContextProvider';
1111
import {EnvironmentProvider} from '@src/components/withEnvironment';
1212
import {KeyboardStateProvider} from '@src/components/withKeyboardState';
13+
import CONST from '@src/CONST';
14+
import IntlStore from '@src/languages/IntlStore';
1315
import ONYXKEYS from '@src/ONYXKEYS';
1416
import './fonts.css';
1517

@@ -20,6 +22,8 @@ Onyx.init({
2022
},
2123
});
2224

25+
IntlStore.load(CONST.LOCALES.EN);
26+
2327
const decorators = [
2428
(Story: React.ElementType) => (
2529
<ComposeProviders

Mobile-Expensify

__mocks__/@ua/react-native-airship.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@ enum EventType {
66
PushReceived = 'com.airship.push_received',
77
}
88

9-
// eslint-disable-next-line no-restricted-syntax
10-
enum PermissionStatus {
11-
Granted = 'granted',
12-
Denied = 'denied',
13-
NotDetermined = 'not_determined',
14-
}
15-
169
// eslint-disable-next-line @typescript-eslint/no-namespace
1710
namespace iOS {
1811
/**
@@ -78,4 +71,4 @@ const Airship: Partial<AirshipRoot> = {
7871

7972
export default Airship;
8073

81-
export {EventType, iOS, PermissionStatus};
74+
export {EventType, iOS};

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 1009018904
118-
versionName "9.1.89-4"
117+
versionCode 1009019100
118+
versionName "9.1.91-0"
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"

assets/images/simple-illustrations/simple-illustration__empty-shelves.svg

Lines changed: 194 additions & 0 deletions
Loading
Lines changed: 16 additions & 0 deletions
Loading

contributingGuides/STYLE.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1269,8 +1269,10 @@ The correct approach is avoid using `ScrollView`. You can add props like `listHe
12691269
</ScrollView>
12701270
```
12711271
1272-
### Correct Approach (Using `SelectionList`)
1272+
### Correct Approach
1273+
The correct approach is to use the list component's built-in header and footer props instead of wrapping in a `ScrollView`:
12731274
1275+
- Using `SelectionList`
12741276
```jsx
12751277
<SelectionList
12761278
sections={[{item}]}
@@ -1281,6 +1283,20 @@ The correct approach is avoid using `ScrollView`. You can add props like `listHe
12811283
/>
12821284
```
12831285
1286+
- If you can't switch to `SelectionList` or `FlatList`, you can use `FlashList` as an alternative approach:
1287+
```jsx
1288+
<ScrollView>
1289+
<Text>Header Content</Text>
1290+
<FlashList
1291+
data={data}
1292+
renderItem={RadioListItem}
1293+
estimatedItemSize={variables.optionRowHeight}
1294+
keyExtractor={keyExtractor}
1295+
/>
1296+
<Button title="Submit" onPress={handleSubmit} />
1297+
</ScrollView>
1298+
```
1299+
12841300
This ensures optimal performance and avoids layout issues.
12851301
12861302

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+
---

0 commit comments

Comments
 (0)