Skip to content

Commit dbecb70

Browse files
committed
Merge branch 'main' into @GCyganek/landscape-mode/report-submit-to-popover
2 parents d72affc + 49ce089 commit dbecb70

680 files changed

Lines changed: 18304 additions & 6993 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/skills/agent-device/SKILL.md

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ allowed-tools: Bash(agent-device *) Bash(npm root *) Bash(scripts/is-hybrid-app.
1010

1111
These checks evaluate at skill load. If any line shows `FAIL`, stop and surface the fix before running any device command.
1212

13-
`agent-device` version: !`R=0.13.0; V=$(agent-device --version 2>/dev/null); [ -n "$V" ] && [ "$(printf '%s\n%s\n' "$R" "$V" | sort -V | head -1)" = "$R" ] && echo "OK ($V)" || echo "FAIL (need v$R+, got: ${V:-not installed}). Fix: npm install -g agent-device@latest"`
13+
`agent-device` version: !`R=0.18.0; V=$(agent-device --version 2>/dev/null); [ -n "$V" ] && [ "$(printf '%s\n%s\n' "$R" "$V" | sort -V | head -1)" = "$R" ] && echo "OK ($V)" || echo "FAIL (need v$R+, got: ${V:-not installed}). Fix: npm install -g agent-device@latest"`
1414

1515
Bundled CLI skills dir: !`D="$(npm root -g)/agent-device/skills/agent-device"; test -s "$D/SKILL.md" && echo "OK ($D)" || echo "FAIL (missing $D/SKILL.md). Fix: npm install -g agent-device@latest"`
1616

@@ -28,18 +28,18 @@ If the user prompt names `ios` or `android` explicitly, use it. Otherwise ask. O
2828

2929
HybridApp dev builds only (the pre-flight gate enforces this).
3030

31-
| Platform | Bundle ID | Build script |
32-
| --------- | ------------------------------- | ----------------- |
33-
| `ios` | `com.expensify.expensifylite` | `npm run ios` |
34-
| `android` | `org.me.mobiexpensifyg.dev` | `npm run android` |
31+
| Platform | Bundle ID | Build command from App root |
32+
| --------- | ------------------------------- | --------------------------- |
33+
| `ios` | `com.expensify.expensifylite` | `npm run ios` |
34+
| `android` | `org.me.mobiexpensifyg.dev` | `npm run android` |
3535

3636
### 3. Confirm dev build is installed
3737

3838
```bash
39-
agent-device apps --user-installed --platform <p> --json
39+
agent-device apps --platform <p> --json
4040
```
4141

42-
If the resolved bundle ID is missing from the list, **STOP** and instruct the developer to run the matching build script from the table. HybridApp mobile builds **must** be initiated from `Mobile-Expensify/` (per project CLAUDE.md).
42+
If the resolved bundle ID is missing from the list, **STOP** and instruct the developer to run the matching build command from the App repository root. The build script detects HybridApp mode and builds the native app from `Mobile-Expensify/`.
4343

4444
### 4. Metro
4545

@@ -89,6 +89,32 @@ agent-device snapshot -i
8989

9090
Confirm the app rendered. From here, follow the [Agent decision loop](flows/README.md) for repeatable flows or drive interactively.
9191

92+
### 9. Interaction safety
93+
94+
After opening or relaunching the App, inspect `agent-device snapshot -i` for React Native development overlays before interacting.
95+
96+
If the snapshot reports a LogBox warning, run:
97+
98+
```bash
99+
agent-device react-native dismiss-overlay
100+
agent-device snapshot -i
101+
```
102+
103+
Continue only when the fresh snapshot no longer reports the overlay. Multiple LogBox banners can require repeated `dismiss-overlay` and fresh `snapshot -i` calls. If the snapshot reports a RedBox fatal error, stop and surface the error instead of dismissing it.
104+
105+
Before pressing an action that may be covered by an overlay or system UI:
106+
107+
```bash
108+
agent-device snapshot -i
109+
agent-device screenshot --overlay-refs
110+
```
111+
112+
Use a stable selector or a fresh `@eN` reference. Confirm the target is reported as hittable. Never use coordinates to bypass `interactionBlocked: "covered"`, `reason: "offscreen_ref"`, or `targetHittable: false`.
113+
114+
When a target is rejected, capture `agent-device screenshot --overlay-refs`. Dismiss a recoverable LogBox overlay when present, capture a fresh `snapshot -i`, and retry only through a selector or fresh reference. Otherwise stop and report the blocker.
115+
116+
After pressing the action, verify the expected destination or control state with `wait`, `is`, `find`, or a fresh snapshot.
117+
92118
### Canonical skill references
93119

94120
Read these files directly for device automation guidance (bootstrap, exploration, verification, debugging): !`echo "$(npm root -g)/agent-device/skills/agent-device"`

.claude/skills/coding-standards/rules/perf-18-use-pre-mount-destination.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ Flag when:
109109
**DO NOT flag if:**
110110

111111
- The code uses `usePreMountDestination` correctly with the matching reveal method for the flow
112-
- The call site is an approved exception (`IOURequestStepConfirmation`, `useSkipConfirmationPreInsert` until migrated)
113112
- The flow uses specialized dismiss helpers that intentionally bypass pre-insert/reveal
114113

115114
**Search patterns:**
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Fetch React Native prebuild files
2+
description: >
3+
Copies the `ios-prebuild*` scripts and `Package.swift` from the upstream
4+
facebook/react-native repo (matching the version pinned in package.json)
5+
into the local node_modules/react-native/ folder.
6+
7+
runs:
8+
using: composite
9+
steps:
10+
- name: Get React Native version
11+
id: rnVersion
12+
shell: bash
13+
run: echo "VERSION=$(jq -r '.dependencies["react-native"]' package.json)" >> "$GITHUB_OUTPUT"
14+
15+
- name: Checkout upstream React Native prebuild files
16+
uses: useblacksmith/checkout@1c9394c220d293645707b625ba9d79685f093a8f # v1
17+
with:
18+
repository: facebook/react-native
19+
ref: v${{ steps.rnVersion.outputs.VERSION }}
20+
path: rn-prebuild
21+
sparse-checkout: packages/react-native/scripts
22+
23+
- name: Copy prebuild files into node_modules
24+
shell: bash
25+
run: |
26+
cp -r rn-prebuild/packages/react-native/scripts/ios-prebuild* node_modules/react-native/scripts/
27+
cp rn-prebuild/packages/react-native/Package.swift node_modules/react-native/
28+
rm -rf rn-prebuild
Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: Set up Node
2-
description: Set up Node
2+
description: |
3+
Set up Node and restore dependencies, picking the strategy by runner OS: Blacksmith Sticky Disks on Linux, actions/cache on macOS.
34
45
inputs:
56
IS_HYBRID_BUILD:
@@ -10,7 +11,7 @@ inputs:
1011
outputs:
1112
cache-hit:
1213
description: Was there a cache hit on the main node_modules?
13-
value: ${{ steps.cache-node-modules.outputs.cache-hit }}
14+
value: ${{ steps.check-node-modules.outputs.cache-hit }}
1415

1516
runs:
1617
using: composite
@@ -24,33 +25,106 @@ runs:
2425
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
2526
with:
2627
node-version-file: '.nvmrc'
27-
cache: 'npm'
28-
cache-dependency-path: normalized-package-lock.json
28+
29+
- id: runner-info
30+
shell: bash
31+
run: echo "use-sticky-disk=${{ runner.os == 'Linux' && contains(runner.name, 'blacksmith') }}" >> "$GITHUB_OUTPUT"
32+
33+
- id: stickydisk-node-modules
34+
if: steps.runner-info.outputs.use-sticky-disk == 'true'
35+
uses: useblacksmith/stickydisk@6d373c96a74cbde0c99fedc5ea5d3a7ba66ba494 # v1.4.0
36+
with:
37+
key: ${{ format('{0}-{1}-{2}-node-modules-{3}', github.repository, runner.os, runner.arch, inputs.IS_HYBRID_BUILD == 'true' && hashFiles('normalized-package-lock.json', 'patches/**', 'Mobile-Expensify/patches/**') || hashFiles('normalized-package-lock.json', 'patches/**')) }}
38+
path: node_modules
39+
40+
- id: stickydisk-old-dot-node-modules
41+
if: steps.runner-info.outputs.use-sticky-disk == 'true' && inputs.IS_HYBRID_BUILD == 'true'
42+
uses: useblacksmith/stickydisk@6d373c96a74cbde0c99fedc5ea5d3a7ba66ba494 # v1.4.0
43+
with:
44+
key: ${{ format('{0}-{1}-{2}-old-dot-node-modules-{3}', github.repository, runner.os, runner.arch, hashFiles('Mobile-Expensify/package-lock.json', 'Mobile-Expensify/patches/**')) }}
45+
path: Mobile-Expensify/node_modules
46+
47+
- id: stickydisk-npm
48+
if: steps.runner-info.outputs.use-sticky-disk == 'true'
49+
uses: useblacksmith/stickydisk@6d373c96a74cbde0c99fedc5ea5d3a7ba66ba494 # v1.4.0
50+
with:
51+
key: ${{ format('{0}-{1}-{2}-npm', github.repository, runner.os, runner.arch) }}
52+
path: ~/.npm
2953

3054
- id: cache-node-modules
55+
if: steps.runner-info.outputs.use-sticky-disk != 'true'
3156
# v5.0.1
3257
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb
3358
with:
3459
path: node_modules
35-
key: ${{ inputs.IS_HYBRID_BUILD == 'true' && format('{0}-node-modules-{1}', runner.os, hashFiles('package-lock.json', 'patches/**', 'Mobile-Expensify/patches/**')) || format('{0}-node-modules-{1}', runner.os, hashFiles('package-lock.json', 'patches/**'))}}
60+
key: ${{ format('{0}-node-modules-{1}', runner.os, inputs.IS_HYBRID_BUILD == 'true' && hashFiles('package-lock.json', 'patches/**', 'Mobile-Expensify/patches/**') || hashFiles('package-lock.json', 'patches/**')) }}
3661

3762
- id: cache-old-dot-node-modules
38-
if: inputs.IS_HYBRID_BUILD == 'true'
63+
if: steps.runner-info.outputs.use-sticky-disk != 'true' && inputs.IS_HYBRID_BUILD == 'true'
3964
# v5.0.1
4065
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb
4166
with:
4267
path: Mobile-Expensify/node_modules
4368
key: ${{ runner.os }}-node-modules-${{ hashFiles('Mobile-Expensify/package-lock.json', 'Mobile-Expensify/patches/**') }}
4469

45-
- name: Remove ND node_modules if needed for hybrid app build
46-
if: inputs.IS_HYBRID_BUILD == 'true' && steps.cache-node-modules.outputs.cache-hit == 'true' && steps.cache-old-dot-node-modules.outputs.cache-hit != 'true'
70+
- id: check-node-modules
4771
shell: bash
48-
run: rm -rf node_modules
72+
env:
73+
USE_STICKY_DISK: ${{ steps.runner-info.outputs.use-sticky-disk }}
74+
ACTIONS_CACHE_HIT: ${{ steps.cache-node-modules.outputs.cache-hit }}
75+
run: |
76+
if [[ "$USE_STICKY_DISK" == 'true' ]]; then
77+
[[ -f node_modules/.stickydisk-installed ]] && CACHE_HIT='true' || CACHE_HIT='false'
78+
else
79+
CACHE_HIT="$ACTIONS_CACHE_HIT"
80+
fi
81+
echo "cache-hit=$CACHE_HIT" >> "$GITHUB_OUTPUT"
82+
83+
- id: check-old-dot-node-modules
84+
if: inputs.IS_HYBRID_BUILD == 'true'
85+
shell: bash
86+
env:
87+
USE_STICKY_DISK: ${{ steps.runner-info.outputs.use-sticky-disk }}
88+
ACTIONS_CACHE_HIT: ${{ steps.cache-old-dot-node-modules.outputs.cache-hit }}
89+
run: |
90+
if [[ "$USE_STICKY_DISK" == 'true' ]]; then
91+
[[ -f Mobile-Expensify/node_modules/.stickydisk-installed ]] && CACHE_HIT='true' || CACHE_HIT='false'
92+
else
93+
CACHE_HIT="$ACTIONS_CACHE_HIT"
94+
fi
95+
echo "cache-hit=$CACHE_HIT" >> "$GITHUB_OUTPUT"
96+
97+
- id: cache-npm
98+
if: steps.runner-info.outputs.use-sticky-disk != 'true' && (steps.check-node-modules.outputs.cache-hit != 'true' || (inputs.IS_HYBRID_BUILD == 'true' && steps.check-old-dot-node-modules.outputs.cache-hit != 'true'))
99+
# v5.0.1
100+
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb
101+
with:
102+
path: ~/.npm
103+
key: ${{ runner.os }}-${{ runner.arch }}-npm-${{ hashFiles('normalized-package-lock.json') }}
104+
restore-keys: ${{ runner.os }}-${{ runner.arch }}-npm-
105+
106+
- name: Clear ND node_modules if needed for hybrid app build
107+
if: inputs.IS_HYBRID_BUILD == 'true' && steps.check-node-modules.outputs.cache-hit == 'true' && steps.check-old-dot-node-modules.outputs.cache-hit != 'true'
108+
shell: bash
109+
# Empty the contents instead of deleting node_modules itself: on Linux it is a sticky-disk mount
110+
# point, so `rm -rf node_modules` would fail to remove the mount (Device or resource busy).
111+
run: find node_modules -mindepth 1 -maxdepth 1 -exec rm -rf {} +
49112

50113
- name: Install root project node packages
51-
if: steps.cache-node-modules.outputs.cache-hit != 'true' || (inputs.IS_HYBRID_BUILD == 'true' && steps.cache-old-dot-node-modules.outputs.cache-hit != 'true')
114+
if: steps.check-node-modules.outputs.cache-hit != 'true' || (inputs.IS_HYBRID_BUILD == 'true' && steps.check-old-dot-node-modules.outputs.cache-hit != 'true')
52115
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4
53116
with:
54117
timeout_minutes: 30
55118
max_attempts: 3
56119
command: npm ci
120+
121+
- name: Mark sticky disks as installed
122+
if: steps.runner-info.outputs.use-sticky-disk == 'true'
123+
shell: bash
124+
env:
125+
IS_HYBRID_BUILD: ${{ inputs.IS_HYBRID_BUILD }}
126+
run: |
127+
touch node_modules/.stickydisk-installed
128+
if [[ "$IS_HYBRID_BUILD" == 'true' ]]; then
129+
touch Mobile-Expensify/node_modules/.stickydisk-installed
130+
fi

.github/actions/javascript/bumpVersion/index.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1976,7 +1976,7 @@ class CLI {
19761976
.map((key) => `[--${key} <value>]`)
19771977
.join(' ');
19781978
const flagUsage = [...Object.keys(flags), '--yes', '--no', '--help'].map((key) => `[${key.startsWith('--') ? key : `--${key}`}]`).join(' ');
1979-
console.log(`\nUsage: npx ts-node ${scriptName} ${flagUsage} ${namedArgUsage} ${positionalUsage}\n`);
1979+
console.log(`\nUsage: ${CLI.detectRuntimeCommand()} ${scriptName} ${flagUsage} ${namedArgUsage} ${positionalUsage}\n`);
19801980
console.log('Flags:');
19811981
for (const [name, spec] of Object.entries(flags)) {
19821982
console.log(` --${name.padEnd(20)} ${spec.description}`);
@@ -2004,6 +2004,25 @@ class CLI {
20042004
console.log('');
20052005
}
20062006
}
2007+
/**
2008+
* Attempts to detect the command used to invoke the currently-running script, for display in the usage line of the help message.
2009+
* Checked in order of most to least reliable signal; falls back to plain `node` if nothing else matches.
2010+
*/
2011+
static detectRuntimeCommand() {
2012+
// Bun's own public, documented signal. Cast needed since @types/node's ProcessVersions type has no `bun` key.
2013+
if (process.versions.bun) {
2014+
return 'bun';
2015+
}
2016+
// Internal symbol ts-node sets on `process` when its register hook is active (the same mechanism other tools, e.g. fastify-autoload, rely on to detect ts-node).
2017+
if (Reflect.get(process, Symbol.for('ts-node.register.instance'))) {
2018+
return 'npx ts-node';
2019+
}
2020+
// tsx registers its loader hooks via --require/--import flags pointing into its own package, visible in execArgv regardless of how it was installed.
2021+
if (process.execArgv.some((arg) => arg.includes('tsx/dist/'))) {
2022+
return 'npx tsx';
2023+
}
2024+
return 'node';
2025+
}
20072026
static parseStringArg(rawString, paramName, spec) {
20082027
if ('parse' in spec && !!spec.parse) {
20092028
try {

0 commit comments

Comments
 (0)