Skip to content

Commit ecf02a9

Browse files
MelvinBottruph01
andcommitted
Merge remote-tracking branch 'origin/main' into claude-reAddMissingCsvImportColumnsWithScrollFix
Co-authored-by: truph01 <truph01@users.noreply.github.com>
2 parents df2c852 + e983645 commit ecf02a9

241 files changed

Lines changed: 7374 additions & 14814 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/commands/review-code-pr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
allowed-tools: Bash(gh pr diff:*),Bash(gh pr view:*)
2+
allowed-tools: Bash(gh pr diff:*),Bash(gh pr view:*),Bash(check-compiler.sh:*)
33
description: Review a code contribution pull request
44
---
55

.claude/scripts/check-compiler.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
3+
# Secure proxy script to run React Compiler compliance check on a single file.
4+
# Validates the filepath before passing it to the underlying npm script.
5+
set -eu
6+
7+
if [[ $# -lt 1 ]]; then
8+
echo "Usage: $0 <filepath>" >&2
9+
exit 1
10+
fi
11+
12+
readonly FILEPATH="$1"
13+
14+
# Strict filepath validation - reject shell metacharacters
15+
if ! [[ "$FILEPATH" =~ ^[a-zA-Z0-9_./@-]+$ ]]; then
16+
echo "Error: Invalid filepath (contains disallowed characters)" >&2
17+
exit 1
18+
fi
19+
20+
npm run react-compiler-compliance-check -- check "$FILEPATH"

.claude/skills/coding-standards/rules/clean-react-0-compiler.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ function Avatar({source, size}: AvatarProps) {
9898
Before flagging, verify that the file actually compiles with React Compiler:
9999

100100
```bash
101-
npx react-compiler-healthcheck --src "<filepath>" --verbose
101+
check-compiler.sh <filepath>
102102
```
103103

104104
If the output contains **"Failed to compile"** for the file under review, the rule **does not apply** — the author may have no alternative to manual memoization until the compilation issue is resolved.

.github/workflows/claude-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
prompt: "/review-code-pr REPO: ${{ github.repository }} PR_NUMBER: ${{ github.event.pull_request.number }}"
7575
claude_args: |
7676
--model claude-opus-4-6
77-
--allowedTools "Task,Glob,Grep,Read,Bash(gh pr diff:*),Bash(gh pr view:*)" --json-schema '${{ steps.schema.outputs.json }}'
77+
--allowedTools "Task,Glob,Grep,Read,Bash(gh pr diff:*),Bash(gh pr view:*),Bash(check-compiler.sh:*)" --json-schema '${{ steps.schema.outputs.json }}'
7878
7979
- name: Post code review results
8080
if: steps.code-review.outcome == 'success' && steps.filter.outputs.code == 'true'

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 1009033901
115-
versionName "9.3.39-1"
114+
versionCode 1009034000
115+
versionName "9.3.40-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"
Lines changed: 1 addition & 0 deletions
Loading
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/NAVIGATION.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The navigation in the app is built on top of the `react-navigation` library. To
2727
- [Entry screens (access control)](#entry-screens-access-control)
2828
- [Current limitations (work in progress)](#current-limitations-work-in-progress)
2929
- [Multi-segment dynamic routes](#multi-segment-dynamic-routes)
30+
- [Suffix layering (stacking dynamic routes)](#suffix-layering-stacking-dynamic-routes)
3031
- [Dynamic routes with query parameters](#dynamic-routes-with-query-parameters)
3132
- [How to add a new dynamic route](#how-to-add-a-new-dynamic-route)
3233
- [Migrating from backTo to dynamic routes](#migrating-from-backto-to-dynamic-routes)
@@ -701,7 +702,7 @@ A dynamic route is a URL suffix (e.g. `verify-account`) that can be appended to
701702

702703
Do not use dynamic routes when:
703704
- Your use case falls under the [current limitations](#current-limitations-work-in-progress):
704-
- You need to stack multiple dynamic route suffixes (e.g. `/a/verify-account/another-flow`).
705+
- You need path parameters in dynamic suffixes (e.g. `a/:reportID`).
705706
- The screen has a single, fixed entry and a fixed back destination. In this case, use a normal static route instead.
706707

707708
### Dynamic routes configuration
@@ -732,10 +733,9 @@ When adding or extending a dynamic route, list every screen that should be able
732733

733734
### Current limitations (work in progress)
734735

735-
- **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.
736736
- **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

738-
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.
738+
If you try to use dynamic routes for this case 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

740740
### Multi-segment dynamic routes
741741

@@ -751,6 +751,61 @@ For instance, if both `verify-account` and `add-bank-account/verify-account`
751751
are registered, a path ending with `/add-bank-account/verify-account`
752752
will always match the longer, more specific suffix.
753753

754+
### Suffix layering (stacking dynamic routes)
755+
756+
Dynamic route suffixes can be stacked on top of each other,
757+
producing URLs like `/base-path/suffix-a/suffix-b`.
758+
Each suffix in the chain is resolved recursively: the parser strips the outermost suffix first,
759+
resolves the remaining path (which may itself contain another dynamic suffix),
760+
and repeats until it reaches a static base path.
761+
762+
For example, given the path `/settings/wallet/verify-account/country`:
763+
764+
1. The outermost suffix `country` is identified and stripped, leaving `/settings/wallet/verify-account`.
765+
2. `/settings/wallet/verify-account` still contains a dynamic suffix `verify-account`, which is stripped to get `/settings/wallet`.
766+
3. `/settings/wallet` is a static path - standard React Navigation parsing returns the base state.
767+
4. The parser walks back up: it checks that the focused screen of `/settings/wallet` is listed in `VERIFY_ACCOUNT.entryScreens`.
768+
5. Then it checks that the focused screen of the resolved `/settings/wallet/verify-account` state is listed in `COUNTRY.entryScreens`.
769+
6. If all authorization checks pass, the final navigation state is built for the full path.
770+
771+
#### Authorization per layer
772+
773+
Each suffix independently validates access via its own `entryScreens` array.
774+
The focused screen resolved from the layer directly beneath must be listed
775+
in the current suffix's `entryScreens`. If any layer fails authorization,
776+
the path falls back to standard React Navigation parsing and a warning is logged.
777+
778+
#### Configuration example
779+
780+
```ts
781+
DYNAMIC_ROUTES: {
782+
VERIFY_ACCOUNT: {
783+
path: 'verify-account',
784+
entryScreens: [SCREENS.SETTINGS.WALLET.ROOT, SCREENS.TRAVEL.MY_TRIPS],
785+
},
786+
ADDRESS_COUNTRY: {
787+
path: 'country',
788+
entryScreens: [SCREENS.SETTINGS.DYNAMIC_VERIFY_ACCOUNT],
789+
getRoute: (country = '') => `country${country ? `?country=${country}` : ''}`,
790+
queryParams: ['country'],
791+
},
792+
},
793+
```
794+
795+
With this configuration, `country` can be opened on top of `verify-account`
796+
because `DYNAMIC_VERIFY_ACCOUNT` is listed in `ADDRESS_COUNTRY.entryScreens`.
797+
Back navigation removes one suffix at a time:
798+
`/settings/wallet/verify-account/country``/settings/wallet/verify-account``/settings/wallet`.
799+
800+
#### Multi-segment suffixes in layered paths
801+
802+
Suffix layering works with multi-segment suffixes as well.
803+
For example, if `deep/verify-account` and `country` are both registered,
804+
the path `/settings/wallet/deep/verify-account/country` will first strip `country`,
805+
then strip `deep/verify-account`, and resolve `/settings/wallet` as the base.
806+
The matching algorithm always tests the longest candidate suffix first,
807+
so overlapping registrations are resolved deterministically.
808+
754809
### Dynamic routes with query parameters
755810

756811
Dynamic route suffixes can carry query parameters

docs/HELPSITE_NAMING_CONVENTIONS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This document governs UI language conventions. It does not define article struct
1010

1111
docs/HELP_AUTHORING_GUIDELINES.md
1212

13+
Note: All article headings (# and ##) must follow the task-based heading rules in HELP_AUTHORING_GUIDELINES.md Section 2, except for `# FAQ` which is exempt. This includes section headings that reference UI features — they must still be task-based, not just feature labels.
14+
1315
---
1416

1517
# Core UI Referencing Rules

0 commit comments

Comments
 (0)