Skip to content

Commit 05a0a1c

Browse files
committed
Merge branch 'main' of github.com:mananjadhav/App into mj-83837-bt-qbo-1
2 parents 8d1dd94 + 9f96f08 commit 05a0a1c

370 files changed

Lines changed: 12584 additions & 11651 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/actions/javascript/getPullRequestIncrementalChanges/index.js

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12410,8 +12410,8 @@ class Git {
1241012410
* @throws Error when git command fails (invalid refs, not a git repo, file not found, etc.)
1241112411
*/
1241212412
static diff(fromRef, toRef, filePaths, shouldIncludeUntrackedFiles = false) {
12413-
// Build git diff command (with 0 context lines for easier parsing)
12414-
let command = `git diff -U0 ${fromRef}`;
12413+
// Build git diff command (with 0 context lines for easier parsing, -M for rename detection)
12414+
let command = `git diff -U0 -M ${fromRef}`;
1241512415
if (toRef) {
1241612416
command += ` ${toRef}`;
1241712417
}
@@ -12453,54 +12453,64 @@ class Git {
1245312453
const files = [];
1245412454
let currentFile = null;
1245512455
let currentHunk = null;
12456-
let oldFilePath = null; // Track old file path to determine fileDiffType
12456+
let oldFilePath = null;
12457+
let renameFromPath = null;
1245712458
for (const line of lines) {
1245812459
// File header: diff --git a/file b/file
1245912460
if (line.startsWith('diff --git')) {
1246012461
if (currentFile) {
12461-
// Push the current hunk to the current file before processing the new file
1246212462
if (currentHunk) {
1246312463
currentFile.hunks.push(currentHunk);
1246412464
}
1246512465
files.push(currentFile);
1246612466
}
1246712467
currentFile = null;
1246812468
currentHunk = null;
12469-
oldFilePath = null; // Reset for next file
12469+
oldFilePath = null;
12470+
renameFromPath = null;
12471+
continue;
12472+
}
12473+
// Rename detection: "rename from <path>" appears before --- / +++
12474+
if (line.startsWith('rename from ')) {
12475+
renameFromPath = line.slice('rename from '.length);
12476+
continue;
12477+
}
12478+
if (line.startsWith('rename to ') || line.startsWith('similarity index ')) {
1247012479
continue;
1247112480
}
1247212481
// Old file path: --- a/file or --- /dev/null (for new files)
12473-
// This comes before +++ in git diff output
1247412482
if (line.startsWith('--- ')) {
12475-
oldFilePath = line.slice(4); // Store the old file path (remove '--- ')
12483+
oldFilePath = line.slice(4);
1247612484
continue;
1247712485
}
1247812486
// New file path: +++ b/file or +++ /dev/null (for removed files)
1247912487
if (line.startsWith('+++ ')) {
12480-
const newFilePath = line.slice(4); // Remove '+++ '
12481-
// Determine fileDiffType based on old and new file paths
12482-
// Note: oldFilePath should always be set by the time we see +++, but handle null for type safety
12488+
const newFilePath = line.slice(4);
1248312489
let fileDiffType = 'modified';
1248412490
let diffFilePath;
12491+
let previousFilePath;
1248512492
const oldPath = oldFilePath ?? '';
1248612493
if (oldPath === '/dev/null') {
12487-
// New file: use the new file path
1248812494
fileDiffType = 'added';
1248912495
diffFilePath = newFilePath.startsWith('b/') ? newFilePath.slice(2) : newFilePath;
1249012496
}
1249112497
else if (newFilePath === '/dev/null') {
12492-
// Removed file: use the old file path
1249312498
fileDiffType = 'removed';
1249412499
diffFilePath = oldPath.startsWith('a/') ? oldPath.slice(2) : oldPath;
1249512500
}
12501+
else if (renameFromPath) {
12502+
fileDiffType = 'renamed';
12503+
diffFilePath = newFilePath.startsWith('b/') ? newFilePath.slice(2) : newFilePath;
12504+
previousFilePath = renameFromPath;
12505+
}
1249612506
else {
12497-
// Modified file: use the new file path
1249812507
fileDiffType = 'modified';
1249912508
diffFilePath = newFilePath.startsWith('b/') ? newFilePath.slice(2) : newFilePath;
1250012509
}
1250112510
currentFile = {
1250212511
filePath: diffFilePath,
1250312512
diffType: fileDiffType,
12513+
previousFilePath,
1250412514
hunks: [],
1250512515
addedLines: new Set(),
1250612516
removedLines: new Set(),
@@ -12723,20 +12733,37 @@ class Git {
1272312733
return false;
1272412734
}
1272512735
}
12726-
static async getChangedFileNames(fromRef, toRef, shouldIncludeUntrackedFiles = false) {
12736+
/**
12737+
* Get changed files with their status (added, modified, removed, renamed).
12738+
* In CI, uses the GitHub API with pagination for accuracy.
12739+
* Locally, uses git diff against the provided ref.
12740+
*/
12741+
static async getChangedFilesWithStatus(fromRef, toRef, shouldIncludeUntrackedFiles = false) {
1272712742
if (IS_CI) {
12728-
const { data: changedFiles } = await GithubUtils_1.default.octokit.pulls.listFiles({
12743+
const files = await GithubUtils_1.default.paginate(GithubUtils_1.default.octokit.pulls.listFiles, {
1272912744
owner: CONST_1.default.GITHUB_OWNER,
1273012745
repo: CONST_1.default.APP_REPO,
1273112746
// eslint-disable-next-line @typescript-eslint/naming-convention
1273212747
pull_number: github_1.context.payload.pull_request?.number ?? 0,
12748+
// eslint-disable-next-line @typescript-eslint/naming-convention
12749+
per_page: 100,
1273312750
});
12734-
return changedFiles.map((file) => file.filename);
12751+
return files.map((file) => ({
12752+
filename: file.filename,
12753+
status: file.status,
12754+
previousFilename: file.previous_filename,
12755+
}));
1273512756
}
12736-
// Get the diff output and check status
1273712757
const diffResult = this.diff(fromRef, toRef, undefined, shouldIncludeUntrackedFiles);
12738-
const files = diffResult.files.map((file) => file.filePath);
12739-
return files;
12758+
return diffResult.files.map((file) => ({
12759+
filename: file.filePath,
12760+
status: file.diffType,
12761+
previousFilename: file.previousFilePath,
12762+
}));
12763+
}
12764+
static async getChangedFileNames(fromRef, toRef, shouldIncludeUntrackedFiles = false) {
12765+
const files = await this.getChangedFilesWithStatus(fromRef, toRef, shouldIncludeUntrackedFiles);
12766+
return files.map((file) => file.filename);
1274012767
}
1274112768
/**
1274212769
* Get list of untracked files from git.

.github/workflows/cherryPick.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ jobs:
279279
channel: '#deployer',
280280
attachments: [{
281281
color: 'good',
282-
text: `🍒 Cherry-pick to *${{ inputs.TARGET }}* successful\nPR: ${{ inputs.PULL_REQUEST_URL || '(version bump only)' }}\nDeploy workflow: ${{ steps.findDeployRun.outputs.DEPLOY_RUN_MESSAGE }}`
282+
text: `🍒 Cherry-pick to *${{ inputs.TARGET }}* successfully started\nPR: ${{ inputs.PULL_REQUEST_URL || '(version bump only)' }}\nDeploy workflow: ${{ steps.findDeployRun.outputs.DEPLOY_RUN_MESSAGE }}`
283283
}]
284284
}
285285
env:
@@ -444,3 +444,20 @@ jobs:
444444
env:
445445
GITHUB_TOKEN: ${{ github.token }}
446446
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
447+
448+
- name: "Announce CP cancellation in #deployer"
449+
uses: 8398a7/action-slack@1750b5085f3ec60384090fb7c52965ef822e869e
450+
if: ${{ cancelled() }}
451+
with:
452+
status: custom
453+
custom_payload: |
454+
{
455+
channel: '#deployer',
456+
attachments: [{
457+
color: 'warning',
458+
text: `🍒 Cherry-pick to *${{ inputs.TARGET }}* was cancelled\nPR: ${{ inputs.PULL_REQUEST_URL || '(version bump only)' }}\nWorkflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}`
459+
}]
460+
}
461+
env:
462+
GITHUB_TOKEN: ${{ github.token }}
463+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

.github/workflows/react-compiler-compliance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
pull_request:
66
types: [opened, synchronize]
77
branches-ignore: [staging, production]
8-
paths: ["**.tsx"]
8+
paths: ["**.ts", "**.tsx"]
99

1010
concurrency:
1111
group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-react-compiler-compliance

.github/workflows/typecheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
# - git diff is used to see the files that were added on this branch
3535
# - gh pr view is used to list files touched by this PR. Git diff may give false positives if the branch isn't up-to-date with main
3636
# - wc counts the words in the result of the intersection
37-
count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.js' '.github/libs/*.js' '.github/scripts/*.js' ':!src/libs/SearchParser/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l)
37+
count_new_js=$(comm -1 -2 <(git diff --name-only --diff-filter=A origin/main HEAD -- 'src/*.js' '__mocks__/*.js' '.storybook/*.js' 'assets/*.js' 'config/*.js' 'jest/*.js' 'scripts/*.js' 'tests/*.js' '.github/libs/*.js' '.github/scripts/*.js' ':!src/libs/SearchParser/*.js' ':!config/babel/*.js') <(gh pr view ${{ github.event.pull_request.number }} --json files | jq -r '.files | map(.path) | .[]') | wc -l)
3838
if [ "$count_new_js" -gt "0" ]; then
3939
echo "ERROR: Found new JavaScript files in the project; use TypeScript instead."
4040
exit 1

Mobile-Expensify

babel.config.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
require('dotenv').config();
22

3+
const BaseReactCompilerConfig = require('./config/babel/reactCompilerConfig');
4+
35
const ReactCompilerConfig = {
4-
target: '19',
5-
environment: {
6-
enableTreatRefLikeIdentifiersAsRefs: true,
7-
},
6+
...BaseReactCompilerConfig,
87
sources: (filename) => !filename.includes('tests/') && !filename.includes('node_modules/'),
98
};
109

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Shared React Compiler configuration used across:
3+
* - babel.config.js (build pipeline, extends with `sources` filter)
4+
* - eslint-plugin-react-compiler-compat (lint-time analysis)
5+
* - react-compiler-compliance-check (CI and local checking)
6+
*
7+
* Intentionally omits `sources` since that's only relevant for the Babel build pipeline.
8+
*/
9+
const ReactCompilerConfig = {
10+
target: '19',
11+
environment: {
12+
enableTreatRefLikeIdentifiersAsRefs: true,
13+
},
14+
};
15+
16+
module.exports = ReactCompilerConfig;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# InteractionManager Migration
2+
3+
## Why
4+
5+
`InteractionManager` is being removed from React Native. We currently maintain a patch to keep it working, but that's a temporary measure and upstream libraries will also drop support over time.
6+
7+
Rather than keep patching, we're replacing `InteractionManager.runAfterInteractions` with purpose-built alternatives that are more precise.
8+
9+
## Current state
10+
11+
`runAfterInteractions` is used across the codebase for a wide range of reasons: waiting for navigation transitions, deferring work after modals close, managing input focus, delaying scroll operations, and many other cases that are hard to classify.
12+
13+
## The problem
14+
15+
`runAfterInteractions` is a global queue with no granularity. This made it a convenient catch-all, but the intent behind each call is often unclear. Many usages exist simply because it "just worked" as a timing workaround, not because it was the right tool for the job.
16+
17+
This makes the migration non-trivial: you have to understand *what each call is actually waiting for* before you can pick the right replacement.
18+
19+
## The approach
20+
21+
**TransitionTracker** is the backbone. It tracks navigation transitions explicitly, so other APIs can hook into transition lifecycle without relying on a global queue.
22+
23+
On top of TransitionTracker, existing APIs gain transition-aware callbacks:
24+
25+
- Navigation methods accept `afterTransition` — a callback that runs after the triggered navigation transition completes
26+
- Navigation methods accept `waitForTransition` — the call waits for all ongoing transitions to finish before navigating
27+
- Keyboard methods accept `afterTransition` — a callback that runs after the keyboard transition completes
28+
- `useConfirmModal` hook's `showConfirmModal` returns a Promise that resolves **after the modal close transition completes**, so any work awaited after it naturally runs post-transition — no explicit `afterTransition` callback needed
29+
30+
This makes the code self-descriptive: instead of a generic `runAfterInteractions`, each call site says exactly what it's waiting for and why.
31+
32+
> **Note:** `TransitionTracker.runAfterTransitions` is an internal primitive. Application code should use the higher-level APIs (`Navigation`, `useConfirmModal`, etc.) rather than importing TransitionTracker directly.
33+
34+
## How
35+
The migration is split into 9 issues. Current status of the migration can be found in the parent Github issue [here](https://github.com/Expensify/App/issues/71913).
36+
37+
## Primitives comparison
38+
39+
For reference, here's how the available timing primitives compare:
40+
41+
### `requestAnimationFrame` (rAF)
42+
43+
- Fires **before the next paint** (~16ms at 60fps)
44+
- Guaranteed to run every frame if the thread isn't blocked
45+
- Use for: UI updates that need to happen on the next frame (scroll, layout measurement, enabling a button after a state flush)
46+
47+
### `requestIdleCallback`
48+
49+
- Fires when the runtime has **idle time** — no pending frames, no urgent work
50+
- May be delayed indefinitely if the main thread stays busy
51+
- Accepts a `timeout` option to force execution after a deadline
52+
- Use for: Non-urgent background work (Pusher subscriptions, search API calls, contact imports)
53+
54+
### `InteractionManager.runAfterInteractions` (legacy — do not use)
55+
56+
- React Native-specific. Fires after all **ongoing interactions** (animations, touches) complete
57+
- Tracks interactions via `createInteractionHandle()` — anything that calls `handle.done()` unblocks the queue
58+
- In practice, this means "run after the current navigation transition finishes"
59+
- Problem: it's a global queue with no granularity — you can't say "after _this specific_ transition"
60+
61+
### Summary
62+
63+
| | Timing | Granularity | Platform |
64+
| ---------------------- | ------------------------- | ------------------------- | --------------------- |
65+
| `rAF` | Next frame (~16ms) | None — just "next paint" | Web + RN |
66+
| `requestIdleCallback` | When idle (unpredictable) | None — "whenever free" | Web + RN (polyfilled) |
67+
| `runAfterInteractions` | After animations finish | Global — all interactions | RN only |

contributingGuides/REACT_COMPILER.md

Lines changed: 16 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,82 +8,39 @@ At Expensify, we are early adopters of this tool and aim to fully leverage its c
88

99
## React Compiler compliance checker
1010

11-
We provide a script, `scripts/react-compiler-compliance-check.ts`, which checks for "Rules of React" compliance locally and enforces these in PRs adding or changing React code through a CI check.
11+
We provide a script, `scripts/react-compiler-compliance-check.ts`, which checks whether React components and hooks compile with React Compiler. It runs in CI on every PR and can also be used locally for quick feedback.
1212

13-
### What it does
13+
### How it works
1414

15-
Runs `react-compiler-healthcheck` in verbose mode, parses output, and summarizes which files compiled and which failed, including file, line, column, and reason. It can:
15+
The script uses `@babel/core`'s `transformSync` with `babel-plugin-react-compiler` directly (no intermediate tools). For each file, the compiler reports whether components/hooks compiled successfully, failed, or weren't found. This produces a three-state result per file: `COMPILED`, `FAILED`, or `SKIPPED` (no components/hooks).
1616

17-
- Check all files or a specific file/glob
18-
- Check only files changed relative to a base branch
19-
- Optionally generate a machine-readable report `react-compiler-report.json`
20-
- Exit with non-zero code when failures are found (useful for CI)
17+
### CI enforcement (two rules)
2118

22-
### Usage
23-
24-
> [!NOTE]
25-
> This script uses `origin` as the base remote by default. If your GH remote is named differently, use the `--remote <name>` flag.
26-
27-
#### Check entire codebase or a specific file/glob
28-
29-
```bash
30-
npm run react-compiler-compliance-check check # Check all files
31-
npm run react-compiler-compliance-check check src/path/Component.tsx # Check specific file
32-
npm run react-compiler-compliance-check check "src/**/*.tsx" # Check glob pattern
33-
```
34-
35-
#### Check only changed files (against main)
36-
37-
```bash
38-
npm run react-compiler-compliance-check check-changed
39-
```
40-
41-
#### Generate a detailed report (saved as `./react-compiler-report.json`)
42-
43-
You can use the `--report` flag with both of the above commands:
19+
The CI check (`check-changed`) enforces two rules on changed `.ts` and `.tsx` files:
4420

45-
```bash
46-
npm run react-compiler-compliance-check check --report
47-
npm run react-compiler-compliance-check check-changed --report
48-
```
49-
50-
#### Additional flags
51-
52-
**Filter by diff changes (`--filterByDiff`)**
21+
1. **New files**: If a new file contains components or hooks that fail to compile, the check fails.
22+
2. **Modified files**: If a file compiled successfully on `main` but fails on the PR branch, the check fails (regression).
5323

54-
Only check files that have been modified in the current diff. This is useful when you want to focus on files that have actual changes:
55-
56-
```bash
57-
npm run react-compiler-compliance-check check --filterByDiff
58-
npm run react-compiler-compliance-check check-changed --filterByDiff
59-
```
24+
Files with no React components or hooks are silently skipped.
6025

61-
**Print successful compilations (`--printSuccesses`)**
26+
### Usage
6227

63-
By default, the script only shows compilation failures. Use this flag to also display files that compiled successfully:
28+
#### Check specific files
6429

6530
```bash
66-
npm run react-compiler-compliance-check check --printSuccesses
67-
npm run react-compiler-compliance-check check-changed --printSuccesses
31+
npm run react-compiler-compliance-check check src/components/Foo.tsx src/hooks/useBar.ts
6832
```
6933

70-
**Custom report filename (`--reportFileName`)**
71-
72-
Specify a custom filename for the generated report instead of the default `react-compiler-report.json`:
34+
#### Check changed files (CI mode, also works locally)
7335

7436
```bash
75-
npm run react-compiler-compliance-check check --report --reportFileName my-custom-report.json
76-
npm run react-compiler-compliance-check check-changed --report --reportFileName my-custom-report.json
37+
npm run react-compiler-compliance-check check-changed
7738
```
7839

79-
**Custom remote name (`--remote`)**
40+
#### Flags
8041

81-
By default, the script uses `origin` as the base remote. If your GitHub remote is named differently, specify it with this flag:
82-
83-
```bash
84-
npm run react-compiler-compliance-check check-changed --remote upstream
85-
npm run react-compiler-compliance-check check --filterByDiff --remote my-remote
86-
```
42+
- `--verbose` — Show detailed output including skipped files and files that compiled successfully.
43+
- `--remote <name>` — Git remote name for the base branch (default: `origin`).
8744

8845
## How to fix a particular problem?
8946

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@
218218
"endcapture",
219219
"enddate",
220220
"endfor",
221+
"endgroup",
221222
"enroute",
222223
"entityid",
223224
"Entra",

0 commit comments

Comments
 (0)