Skip to content

Commit ec6148f

Browse files
committed
Merge main
2 parents fae505a + 4879560 commit ec6148f

239 files changed

Lines changed: 5847 additions & 1906 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.

.claude/agents/code-inline-reviewer.md

Lines changed: 144 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,58 +66,48 @@ Bad:
6666

6767
---
6868

69-
### [PERF-2] Use early returns in array iteration methods
69+
### [PERF-2] Return early before expensive work
7070

71-
- **Search patterns**: `.every(`, `.some(`, `.find(`, `.filter(`
71+
- **Search patterns**: Function bodies where `if (!param)` or `if (param === undefined)` appears AFTER function calls that use `param`
7272

7373
- **Condition**: Flag ONLY when ALL of these are true:
7474

75-
- Using .every(), .some(), .find(), .filter() or similar function
76-
- Function contains an "expensive operation" (defined below)
77-
- There exists a simple property check that could eliminate items earlier
78-
- The simple check is performed AFTER the expensive operation
79-
80-
**Expensive operations are**:
81-
82-
- Function calls (except simple getters/property access)
83-
- Regular expressions
84-
- Object/array iterations
85-
- Math calculations beyond basic arithmetic
86-
87-
**Simple checks are**:
88-
89-
- Property existence (!obj.prop, obj.prop === undefined)
90-
- Boolean checks (obj.isActive)
91-
- Primitive comparisons (obj.id === 5)
92-
- Type checks (typeof, Array.isArray)
75+
- Code performs expensive work (function calls, iterations, API/Onyx reads)
76+
- A simple check could short-circuit earlier
77+
- The simple check happens AFTER the expensive work
9378

9479
**DO NOT flag if**:
9580

96-
- No expensive operations exist
97-
- Simple checks are already done first
98-
- The expensive operation MUST run for all items (e.g., for side effects)
81+
- Simple checks already come first
82+
- Validation requires the computed result
83+
- Expensive work must run for side effects
9984

100-
- **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.
85+
- **Reasoning**: Early returns prevent wasted computation. Validate inputs before passing them to expensive operations.
10186

10287
Good:
10388

10489
```ts
105-
const areAllTransactionsValid = transactions.every((transaction) => {
106-
if (!transaction.rawData || transaction.amount <= 0) {
107-
return false;
90+
function clearReportActionErrors(reportID: string, reportAction: ReportAction) {
91+
if (!reportAction?.reportActionID) {
92+
return;
10893
}
109-
const validation = validateTransaction(transaction);
110-
return validation.isValid;
111-
});
94+
95+
const originalReportID = getOriginalReportID(reportID, reportAction);
96+
// ...
97+
}
11298
```
11399

114100
Bad:
115101

116102
```ts
117-
const areAllTransactionsValid = transactions.every((transaction) => {
118-
const validation = validateTransaction(transaction);
119-
return validation.isValid;
120-
});
103+
function clearReportActionErrors(reportID: string, reportAction: ReportAction) {
104+
const originalReportID = getOriginalReportID(reportID, reportAction);
105+
106+
if (!reportAction?.reportActionID) {
107+
return;
108+
}
109+
// ...
110+
}
121111
```
122112

123113
---
@@ -996,6 +986,8 @@ function ReportScreen({ params: { reportID }}) {
996986

997987
- **Reasoning**: When parent components compute and pass behavioral state to children, if a child's requirements change, then parent components must change as well, increasing coupling and causing behavior to leak across concerns. Letting components own their behavior keeps logic local, allows independent evolution, and follows the principle: "If removing a child breaks parent behavior, coupling exists."
998988

989+
**Distinction from CLEAN-REACT-PATTERNS-3**: This rule is about data flow DOWN (parent → child) — "Don't pass data the child can get itself."
990+
999991
Good (component owns its behavior):
1000992

1001993
- Component receives only IDs and handlers
@@ -1079,6 +1071,124 @@ In this example:
10791071

10801072
---
10811073

1074+
### [CLEAN-REACT-PATTERNS-3] Design context-free component contracts
1075+
1076+
- **Search patterns**: Callback props with consumer-specific signatures like `(index: number) => void`, props used only to extract values for callbacks, refs used to access external component state, useImperativeHandle
1077+
1078+
- **Condition**: Flag ONLY when BOTH of these are true:
1079+
1080+
1. A component's interface is shaped around a specific consumer's implementation rather than abstract capabilities
1081+
2. AND at least ONE of the following manifestations is present:
1082+
- The component receives data only to extract values for callbacks (doesn't use it for rendering)
1083+
- Callback signatures encode consumer-specific assumptions (e.g., `(index: number) => void` for navigation)
1084+
- The component accesses external state through refs or imperative handles
1085+
1086+
**Signs of violation:**
1087+
- Callback signatures that encode consumer assumptions: `navigateToWaypoint(index: number)` instead of `onAddWaypoint()`
1088+
- Props passed only to extract values for callbacks, not for rendering (e.g., `transaction` passed just to compute `waypoints.length`)
1089+
- Imperative access to external state via refs or `useImperativeHandle`
1090+
- Component requires modification to work in a different context
1091+
1092+
**DO NOT flag if:**
1093+
- Component signals events with data it naturally owns (e.g., `onChange(value)` for an input, `onSelectItem(item)` for a list)
1094+
- Callbacks are abstract actions the component can trigger (e.g., `onAddStop()`, `onSubmit()`)
1095+
- State coordination happens at a higher level with clear data flow
1096+
1097+
**What makes a contract "abstract":**
1098+
- Callback describes *what happened* in component terms: `onAddStop`, `onSelect`, `onChange`
1099+
- Callback does NOT describe *what consumer should do*: `navigateToWaypoint(index)`, `updateParentState(value)`
1100+
- Props are used for rendering or internal logic, not just to compute callback arguments
1101+
- Component works without modification in a different context
1102+
1103+
- **Reasoning**: A component's contract should expose its capabilities abstractly, not encode assumptions about how it will be used. When interfaces leak consumer-specific details, the component becomes coupled to that context and requires modification for reuse. Good contracts signal *what the component can do*, not *what the consumer needs*.
1104+
1105+
**Distinction from CLEAN-REACT-PATTERNS-2**: PATTERNS-2 ensures components fetch their own data. This rule ensures components expose abstract capabilities, not consumer-specific interfaces.
1106+
1107+
Good (abstract contract):
1108+
1109+
- Interface exposes capability: "user can add a stop"
1110+
- Implementation details (index computation, navigation) stay with consumer
1111+
- Component is reusable in any context needing an "add stop" action
1112+
1113+
```tsx
1114+
<DistanceRequestFooter
1115+
onAddStop={() => {
1116+
const nextIndex = Object.keys(transaction?.comment?.waypoints ?? {}).length;
1117+
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_WAYPOINT.getRoute(..., nextIndex.toString(), ...));
1118+
}}
1119+
/>
1120+
1121+
// in DistanceRequestFooter
1122+
<Button onPress={onAddStop}>{translate('distance.addStop')}</Button>
1123+
```
1124+
1125+
Bad (contract leaks consumer assumptions):
1126+
1127+
- Callback `navigateToWaypointEditPage(index: number)` encodes routing assumption
1128+
- `transaction` prop exists only to compute index for callback
1129+
- Requires modification if consumer navigates differently
1130+
1131+
```tsx
1132+
type DistanceRequestFooterProps = {
1133+
waypoints?: WaypointCollection;
1134+
navigateToWaypointEditPage: (index: number) => void; // Encodes routing assumption
1135+
transaction: OnyxEntry<Transaction>;
1136+
policy: OnyxEntry<Policy>;
1137+
};
1138+
1139+
// in IOURequestStepDistance
1140+
<DistanceRequestFooter
1141+
waypoints={waypoints}
1142+
navigateToWaypointEditPage={navigateToWaypointEditPage}
1143+
transaction={transaction}
1144+
policy={policy}
1145+
/>
1146+
1147+
// in DistanceRequestFooter - computes value for consumer's callback
1148+
<Button
1149+
onPress={() => navigateToWaypointEditPage(Object.keys(transaction?.comment?.waypoints ?? {}).length)}
1150+
text={translate('distance.addStop')}
1151+
/>
1152+
```
1153+
1154+
Good (independent contracts):
1155+
1156+
- Each component has a self-contained interface
1157+
- State coordination happens at composition level
1158+
1159+
```tsx
1160+
function EditProfile() {
1161+
const [formData, setFormData] = useState<FormData>();
1162+
return (
1163+
<>
1164+
<Form onChangeFormData={setFormData} />
1165+
<SaveButton onSave={() => API.save(formData)} />
1166+
</>
1167+
);
1168+
}
1169+
```
1170+
1171+
Bad (coupled contracts):
1172+
1173+
- `SaveButton` interface requires knowledge of `Form`'s internals
1174+
- Neither component works independently
1175+
1176+
```tsx
1177+
function SaveButton({ getSiblingFormData }: { getSiblingFormData: () => FormData }) {
1178+
const handleSave = () => {
1179+
const formData = getSiblingFormData(); // Reaches into sibling
1180+
API.save(formData);
1181+
};
1182+
return <Button onPress={handleSave}>Save</Button>;
1183+
}
1184+
1185+
// Parent wires siblings together
1186+
<Form ref={formRef} />
1187+
<SaveButton getSiblingFormData={() => formRef.current?.getData()} />
1188+
```
1189+
1190+
---
1191+
10821192
## Instructions
10831193

10841194
1. **First, get the list of changed files and their diffs:**

.github/workflows/testBuildOnPush.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
branches: [main]
66
paths-ignore: ['docs/**', 'contributingGuides/**', 'help/**', '.github/**', 'scripts/**', 'tests/**', 'jest/**', '.claude/**']
77

8+
concurrency:
9+
group: 'testBuildOnPush'
10+
cancel-in-progress: false
11+
812
jobs:
913
prep:
1014
runs-on: ubuntu-latest
@@ -432,7 +436,6 @@ jobs:
432436
summary.addRaw(`\n**Mobile-Expensify Submodule SHA:** [${submoduleSHA}](${submoduleUrl})`);
433437
}
434438
435-
436439
const prUrl = '${{ needs.prep.outputs.PR_URL }}';
437440
438441
if (prUrl) {
@@ -441,5 +444,4 @@ jobs:
441444
summary.addRaw(`\n**PR Link:** No PR associated with this commit.`);
442445
}
443446
444-
445447
summary.write();

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 1009031149
118-
versionName "9.3.11-49"
117+
versionCode 1009031153
118+
versionName "9.3.11-53"
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"
-350 Bytes
Loading

contributingGuides/CONTRIBUTING.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,16 @@ This helps future investigators understand the history and current status of err
193193
16. Please pay attention to the pull request template, especially to how we link PRs with issues they fix. Make sure you don't use GitHub keywords such as `fixes` in your PR description, as this can break our current automated steps for issue management. Follow the PR template format carefully.
194194
17. Upon submission of a PR, please include a numbered list of explicit testing steps for each platform (Web, iOS, Android, and Mobile Web) to confirm the fix works as expected and there are no regressions.
195195
18. Please add a screenshot of the app running on each platform (Web, iOS, Android, Mobile Web).
196+
19. Please review the [PR Authoring & Reviewing Best Practices](./PR_AUTHOR_REVIEWER_BEST_PRACTICES.md) for standards on PR titles, testing responsibilities, and the review workflow.
196197
197198
### Completing the final checklist
198-
19. Once your PR has been deployed to production, a checklist will automatically be commented in the GH issue. You're required to complete the steps that have your name mentioned before payment will be issued.
199-
20. The items requiring your completion consist of:
199+
20. Once your PR has been deployed to production, a checklist will automatically be commented in the GH issue. You're required to complete the steps that have your name mentioned before payment will be issued.
200+
21. The items requiring your completion consist of:
200201
1. Proposing steps to take for a regression test to ensure the bug doesn't occur again (For information on how to successfully complete this, head [here](https://github.com/Expensify/App/blob/main/contributingGuides/REGRESSION_TEST_BEST_PRACTICES.md)).
201202
2. Identifying and noting the offending PR that caused the bug (if any).
202203
3. Commenting on the offending PR to note the bug it caused and why (if applicable).
203204
4. Starting a conversation on if any additional steps should be taken to prevent further bugs similar to the one fixed from occurring again.
204-
21. Once the above items have been successfully completed, then payments will begin to be issued.
205+
22. Once the above items have been successfully completed, then payments will begin to be issued.
205206
206207
### Timeline expectations and asking for help along the way
207208
- If you have made a change to your pull request and are ready for another review, leave a comment that says "Updated" on the pull request itself.

contributingGuides/HOW_TO_BECOME_A_CONTRIBUTOR_PLUS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ C+ are contributors who are experienced at working with Expensify and have gaine
1919
- Follow our Code of Conduct, Contributing.md and README.md docs and processes.
2020
- Comment and fix bugs in a timely manner.
2121
- Clear communicator.
22+
- Familiar with [PR Authoring & Reviewing Best Practices](./PR_AUTHOR_REVIEWER_BEST_PRACTICES.md).
2223
- Bonus points:
2324
- Help other contributors by commenting on their issues.
2425
- Actively involved in the #expensify-open-source slack channel.

0 commit comments

Comments
 (0)