Skip to content

Commit 4e8c768

Browse files
committed
Merge remote-tracking branch 'origin/main' into nicolas-ios-files-app-data-exposure
# Conflicts: # Mobile-Expensify # patches/react-native-nitro-sqlite/details.md
2 parents 12edd32 + 90dd1c1 commit 4e8c768

1,107 files changed

Lines changed: 25730 additions & 10246 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/flows/lib/sign-in-drive.sh

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ fi
3535

3636
# --- selector constants (one source of truth for .ad fallback, classifiers, waits) ---
3737
readonly SEL_LOGIN_FIELD='id="username" || role="textfield" label="Phone or email" || label="Phone or email"'
38+
# Mirrors the fill selector in sign-in.ad. Waiting on SEL_LOGIN_FIELD only proves the field exists, and the macro fills it requiring editable=true, so the replay could start against a field that had rendered but was not yet interactive.
39+
readonly SEL_LOGIN_FIELD_EDITABLE='id="username" editable=true || role="textfield" label="Phone or email" editable=true || label="Phone or email" editable=true'
3840
readonly SEL_CONTINUE='role="button" label="Continue" || label="Continue"'
3941
readonly SEL_NAME_FIELD='label="First name" || label="Full name" || role="textfield"'
4042
# Web wait-union markers. Apostrophe-free substrings dodge the straight-vs-curly
@@ -75,12 +77,21 @@ take_snap() {
7577

7678
snap_has() { printf '%s' "$SNAP" | grep -qiF -- "$1"; }
7779

80+
# Internal drive output lands here, never stdout — callers may parse stdout as a
81+
# machine protocol, and --settle returns a full settled diff.
82+
drive_log() {
83+
local dir="${GITHUB_WORKSPACE:-/tmp}/artifacts"
84+
mkdir -p "$dir" 2>/dev/null || true
85+
printf '%s/melvin-drive-%s.log' "$dir" "${SESSION:-unknown}"
86+
}
87+
7888
# Native verb per platform: press on android, click on web.
7989
press_label() {
8090
local sel="role=\"button\" label=\"$1\" || label=\"$1\""
8191
local verb=click
8292
[[ "$PLATFORM" = android ]] && verb=press
83-
agent-device "$verb" "$sel" --platform "$PLATFORM" --session "$SESSION" 2>/dev/null || true
93+
agent-device "$verb" "$sel" --settle --platform "$PLATFORM" --session "$SESSION" \
94+
>>"$(drive_log)" 2>&1 || true
8495
}
8596

8697
# Dismiss splash / runtime permission / ANR overlays that block the login hierarchy.
@@ -166,7 +177,7 @@ wait_for_login_field() {
166177
local budget_secs="${MELVIN_LOGIN_WAIT_SECS:-40}"
167178
mkdir -p "${GITHUB_WORKSPACE:-/tmp}/artifacts"
168179
if [[ "$PLATFORM" = web ]]; then
169-
agent-device wait "$SEL_LOGIN_FIELD || $WAIT_ONBOARDING" $((budget_secs * 1000)) \
180+
agent-device wait "$SEL_LOGIN_FIELD_EDITABLE || $WAIT_ONBOARDING" $((budget_secs * 1000)) \
170181
--platform "$PLATFORM" --session "$SESSION" >/dev/null 2>&1 || return 1
171182
take_snap
172183
if snap_login_field; then
@@ -218,8 +229,8 @@ clear_onboarding() {
218229
if snap_has '"First name"' || snap_has '"Full name"' \
219230
|| snap_has "What's your name?" || snap_has $'What\u2019s your name?'; then
220231
human "sign-in-drive: onboarding — name MelvinBot (${step})"
221-
agent-device fill "$SEL_NAME_FIELD" 'MelvinBot' \
222-
--platform "$PLATFORM" --session "$SESSION" 2>/dev/null || true
232+
agent-device fill "$SEL_NAME_FIELD" 'MelvinBot' --settle \
233+
--platform "$PLATFORM" --session "$SESSION" >>"$(drive_log)" 2>&1 || true
223234
press_label "Continue"
224235
sleep 1
225236
continue
@@ -301,7 +312,14 @@ drive_sign_in() {
301312
fi
302313
human "sign-in-drive: replay ${sign_in_ad}"
303314
# Replay must share AGENT_DEVICE_STATE_DIR with open (caller exports it).
304-
if ! agent-device replay "$sign_in_ad" -e "EMAIL=${email}" --platform "$PLATFORM" --session "$SESSION"; then
315+
# Keep replay's stderr: it names the diverging step, selector, and repair hint.
316+
mkdir -p "${GITHUB_WORKSPACE:-/tmp}/artifacts"
317+
local replay_log="${GITHUB_WORKSPACE:-/tmp}/artifacts/melvin-signin-replay-${SESSION}.log"
318+
if ! agent-device replay "$sign_in_ad" -e "EMAIL=${email}" --platform "$PLATFORM" --session "$SESSION" \
319+
>>"$(drive_log)" 2>"$replay_log"; then
320+
local replay_err
321+
replay_err="$(tail -n 5 "$replay_log" 2>/dev/null | tr '\n' ' ')"
322+
[[ -n "$replay_err" ]] && human "sign-in-drive: replay reported: ${replay_err}"
305323
# Already past login (onboarding residual / signup REPLACE) — clear, don't re-fill.
306324
take_snap
307325
if snap_onboarding || snap_has '"Skip"'; then
@@ -316,12 +334,16 @@ drive_sign_in() {
316334
# already saw the field. Fall back to direct fill+press.
317335
if snap_login_field; then
318336
human "sign-in-drive: replay wait flaked — direct fill for ${email}"
319-
if agent-device fill "$SEL_LOGIN_FIELD" "${email}" \
320-
--platform "$PLATFORM" --session "$SESSION" \
321-
&& agent-device press "$SEL_CONTINUE" \
322-
--platform "$PLATFORM" --session "$SESSION"; then
337+
local fallback_log="${GITHUB_WORKSPACE:-/tmp}/artifacts/melvin-signin-fallback-${SESSION}.log"
338+
if agent-device fill "$SEL_LOGIN_FIELD" "${email}" --settle \
339+
--platform "$PLATFORM" --session "$SESSION" >"$fallback_log" 2>&1 \
340+
&& agent-device press "$SEL_CONTINUE" --settle \
341+
--platform "$PLATFORM" --session "$SESSION" >>"$fallback_log" 2>&1; then
323342
true
324343
else
344+
local fallback_err
345+
fallback_err="$(tail -n 5 "$fallback_log" 2>/dev/null | tr '\n' ' ')"
346+
[[ -n "$fallback_err" ]] && human "sign-in-drive: fallback fill/press reported: ${fallback_err}"
325347
human "sign-in-drive: direct fill failed for ${email}"
326348
return 1
327349
fi

.claude/skills/coding-standards/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Coding standards for the Expensify App. Each standard is a standalone file in `r
5555
- [CONSISTENCY-14](rules/consistency-14-new-file-header.md) — Non-trivial new files start with a header description
5656
- [CONSISTENCY-15](rules/consistency-15-comment-why.md) — Comments explain why the code exists, not what it does
5757
- [CONSISTENCY-16](rules/consistency-16-plain-comment-style.md) — Write comments as plain, natural sentences
58+
- [CONSISTENCY-17](rules/consistency-17-no-ai-jargon.md) — No AI-generated jargon in code or comments
5859

5960
### Clean React Patterns
6061
- [CLEAN-REACT-PATTERNS-0](rules/clean-react-0-compiler.md) — React Compiler compliance
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
ruleId: CONSISTENCY-17
3+
title: No AI-generated jargon in code or comments
4+
---
5+
6+
## [CONSISTENCY-17] No AI-generated jargon in code or comments
7+
8+
### Reasoning
9+
10+
Certain phrases appear constantly in AI-generated code but rarely in code written by engineers. They make the codebase sound like it was written by a chatbot and should be replaced with plain, direct language.
11+
12+
### Banned phrases
13+
14+
| Phrase | Plain substitute |
15+
|--------|-----------------|
16+
| sentinel | placeholder, marker, guard entry |
17+
| fan out | send, make, dispatch, distribute |
18+
| carve out | set aside, exclude, separate |
19+
| defense in depth | extra guard, additional check |
20+
| belt and suspenders / belt-and-suspenders | extra safety check, redundant guard |
21+
| fresh evidence | new data, updated result |
22+
23+
### Incorrect
24+
25+
```ts
26+
// Fan out the request to every matching snapshot.
27+
function getSentinelValue() { ... }
28+
const fanOutRequests = () => { ... }
29+
30+
// Defense in depth: reject the value if it arrived stale.
31+
// Belt-and-suspenders check before writing.
32+
// Uses a sentinel to signal end-of-stream.
33+
```
34+
35+
### Correct
36+
37+
```ts
38+
// Send the request to every matching snapshot.
39+
function getPlaceholderValue() { ... }
40+
const sendDuplicateRequests = () => { ... }
41+
42+
// Additional guard: reject the value if it arrived stale.
43+
// Extra safety check before writing.
44+
// Uses a placeholder to signal end-of-stream.
45+
```
46+
47+
---
48+
49+
### Review Metadata
50+
51+
Flag when any added or modified code — including comments, function names, variable names, type names, or string literals — contains one of the banned phrases above.
52+
53+
**DO NOT flag if:**
54+
55+
- The phrase appears inside a quoted external API name, a third-party library identifier, or a value the codebase does not control (e.g. a server response field name)
56+
- The phrase is in a test description string that is directly testing behavior described by an external spec or API that uses the term
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Get Xcode version
2+
description: Returns the Xcode version and exports DEVELOPER_DIR for the pinned toolchain
3+
4+
outputs:
5+
version:
6+
description: The Xcode version
7+
value: ${{ steps.getVersion.outputs.VERSION }}
8+
9+
runs:
10+
using: composite
11+
steps:
12+
- name: Get Xcode version
13+
id: getVersion
14+
shell: bash
15+
run: |
16+
VERSION="26.6"
17+
DEVELOPER_DIR="/Applications/Xcode_${VERSION}.app/Contents/Developer"
18+
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
19+
echo "DEVELOPER_DIR=$DEVELOPER_DIR" >> "$GITHUB_ENV"

.github/workflows/buildIOS.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ jobs:
5050
name: Build iOS HybridApp
5151
runs-on: blacksmith-12vcpu-macos-latest
5252
env:
53-
DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer
5453
PULL_REQUEST_NUMBER: ${{ inputs.pull-request-number }}
5554
outputs:
5655
IOS_VERSION: ${{ steps.getIOSVersion.outputs.IOS_VERSION }}
@@ -74,6 +73,10 @@ jobs:
7473
git fetch origin ${{ inputs.mobile-expensify-ref }}
7574
git checkout ${{ inputs.mobile-expensify-ref }}
7675
76+
- name: Set up Xcode version
77+
id: xcodeVersion
78+
uses: ./.github/actions/composite/getXcodeVersion
79+
7780
- name: Compute custom build identifier
7881
id: computeIdentifier
7982
run: |
@@ -115,7 +118,7 @@ jobs:
115118
id: pods-cache
116119
with:
117120
path: Mobile-Expensify/iOS/Pods
118-
key: ${{ runner.os }}-pods-cache-${{ hashFiles('Mobile-Expensify/iOS/Podfile.lock', 'firebase.json') }}
121+
key: ${{ runner.os }}-pods-cache-${{ steps.xcodeVersion.outputs.version }}-${{ hashFiles('Mobile-Expensify/iOS/Podfile.lock', 'firebase.json') }}
119122

120123
- name: Compare Podfile.lock and Manifest.lock
121124
id: compare-podfile-and-manifest

.github/workflows/deploy.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,6 @@ jobs:
441441
needs: [prep, iosBuild]
442442
runs-on: blacksmith-12vcpu-macos-latest
443443
if: ${{ fromJSON(needs.prep.outputs.SHOULD_BUILD_NATIVE) }}
444-
env:
445-
DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer
446444
steps:
447445
- name: Checkout
448446
# Upstream checkout on macOS - Blacksmith's git-mirror cache can't work there (see workflows/README.md)
@@ -452,6 +450,9 @@ jobs:
452450
token: ${{ secrets.OS_BOTIFY_TOKEN }}
453451
submodules: true
454452

453+
- name: Set up Xcode version
454+
uses: ./.github/actions/composite/getXcodeVersion
455+
455456
- name: Download iOS build artifact
456457
# v7
457458
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
@@ -522,15 +523,16 @@ jobs:
522523
needs: [prep, iosBuild, iosUploadTestflight]
523524
runs-on: blacksmith-12vcpu-macos-latest
524525
if: ${{ always() && !cancelled() && needs.prep.outputs.DEPLOY_ENV == 'production' && needs.iosBuild.result != 'failure' && needs.iosUploadTestflight.result != 'failure' }}
525-
env:
526-
DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer
527526
steps:
528527
- name: Checkout
529528
# Upstream checkout on macOS - Blacksmith's git-mirror cache can't work there (see workflows/README.md)
530529
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
531530
with:
532531
ref: ${{ needs.prep.outputs.DEPLOY_SHA }}
533532

533+
- name: Set up Xcode version
534+
uses: ./.github/actions/composite/getXcodeVersion
535+
534536
- name: Setup Ruby
535537
# v1.310.0
536538
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f
@@ -940,8 +942,9 @@ jobs:
940942
autoRetestRequestForCP:
941943
name: File retest request for cherry-picked deploy-blocker fixes
942944
runs-on: blacksmith-2vcpu-ubuntu-2404
943-
# Only for a cherry-pick to staging, once every platform is on staging.
944-
if: ${{ github.repository == 'Expensify/App' && needs.prep.outputs.DEPLOY_ENV == 'staging' && fromJSON(needs.prep.outputs.IS_CHERRY_PICK) && fromJSON(needs.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED) }}
945+
# androidSubmit/iosSubmit always skip on staging (they're prod-only), so the implicit
946+
# success() check would skip this job too unless we override it with always().
947+
if: ${{ always() && github.repository == 'Expensify/App' && needs.prep.outputs.DEPLOY_ENV == 'staging' && fromJSON(needs.prep.outputs.IS_CHERRY_PICK) && fromJSON(needs.checkDeploymentSuccess.outputs.IS_ALL_PLATFORMS_DEPLOYED) }}
945948
needs: [prep, checkDeploymentSuccess]
946949
steps:
947950
- name: Checkout

.github/workflows/publishReactNativeiOSArtifacts.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ on:
2525
description: Webhook used to announce failures.
2626
required: true
2727

28-
env:
29-
DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer
30-
3128
jobs:
3229
buildSlice:
3330
name: Build iOS Slice
@@ -62,6 +59,9 @@ jobs:
6259
with:
6360
IS_HYBRID_BUILD: ${{ matrix.is_hybrid }}
6461

62+
- name: Set up Xcode version
63+
uses: ./.github/actions/composite/getXcodeVersion
64+
6565
- name: Resolve Hermes version
6666
id: hermes
6767
run: echo "VERSION=$(sed 's/^hermes-v\{0,1\}//' node_modules/react-native/sdks/.hermesversion)" >> "$GITHUB_OUTPUT"
@@ -129,6 +129,9 @@ jobs:
129129
- name: Fetch prebuild files from upstream RN
130130
uses: ./.github/actions/composite/fetchReactNativePrebuild
131131

132+
- name: Set up Xcode version
133+
uses: ./.github/actions/composite/getXcodeVersion
134+
132135
- name: Download slice artifacts
133136
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
134137
with:

.github/workflows/remote-build-ios.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ jobs:
4646
build:
4747
needs: [prep, resolveRefs]
4848
runs-on: ${{ github.repository_owner == 'Expensify' && 'blacksmith-12vcpu-macos-latest' || 'macos-latest' }}
49-
env:
50-
DEVELOPER_DIR: /Applications/Xcode_26.2.app/Contents/Developer
5149
strategy:
5250
fail-fast: false
5351
matrix:
@@ -76,6 +74,9 @@ jobs:
7674
git checkout ${{ needs.resolveRefs.outputs.MOBILE_EXPENSIFY_REF }}
7775
echo "Checked out Mobile-Expensify PR #${{ needs.resolveRefs.outputs.MOBILE_EXPENSIFY_PR }} (${{ needs.resolveRefs.outputs.MOBILE_EXPENSIFY_REF }})"
7876
77+
- name: Set up Xcode version
78+
uses: ./.github/actions/composite/getXcodeVersion
79+
7980
- name: Setup Node
8081
uses: ./.github/actions/composite/setupNode
8182
with:

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ Key GitHub Actions workflows:
186186
- **Critical**: All mobile builds originate from this directory
187187
- Contains platform-specific code for iOS and Android
188188
- Manages the HybridApp integration layer
189+
- **Submodule pointer**: bumped automatically by OSBotify on every merge to Mobile-Expensify `main`
189190

190191
### expensify-common
191192

Mobile-Expensify

0 commit comments

Comments
 (0)