Skip to content

Commit 283fe3b

Browse files
authored
Merge branch 'Expensify:main' into invitemoney
2 parents c176204 + f2d51e4 commit 283fe3b

1,289 files changed

Lines changed: 41303 additions & 16762 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/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,29 @@
33
## Directory layout
44

55
- `macros/` - reusable helpers for common setup/navigation actions that stop in a navigable state for further interactive work.
6+
- `macros/<platform>/` - platform-specific overrides of a `macros/` flow, for flows whose selectors differ per platform. See [Platform scoping](#platform-scoping).
67
- `tests/` - critical-scenario scripts for QA/perf verification that assert explicit outcomes (for example Sentry spans) and then stop.
78
- `lib/` - bash drive libraries for flows that need conditional steering the linear `.ad` format cannot express (snapshot classification, state-dependent branching). Each file documents its own contract; the caller always owns the session lifecycle (`open`/`close`/`record`). Source them from an orchestrator or run them standalone against an already-open session (for example `lib/sign-in-drive.sh --platform web --session <name> --email <email>`).
89

910
Composable `.ad` snippets - bounded units of work. A flow may span one or multiple screens as long as it represents a coherent, reusable action with clear start (`@pre`) and completion (`@post`) checkpoints. Each flow advertises machine-matchable metadata (`@pre`, `@post`, `@tag`, `@param`) via `# @`-prefixed comment headers, while flow type is derived from location (`flows/macros/` or `flows/tests/`).
1011

12+
## Platform scoping
13+
14+
Most flows are platform-neutral and live directly in `macros/`. A flow whose selectors genuinely differ per platform gets a copy per platform under `macros/<platform>/`, where `<platform>` is the value passed to `--platform`.
15+
16+
A caller driving platform `P` resolves a macro by name:
17+
18+
1. `macros/<P>/<name>.ad` when that file exists.
19+
2. `macros/<name>.ad` otherwise.
20+
21+
Split flows today: `sign-in.ad`, `send-message.ad`, `complete-onboarding.ad`. All three fill text inputs, whose accessibility shape differs between web and native. The unscoped copy of a split flow stays in `macros/` as the fallback for platforms that have no folder yet; it is not the contract for any platform that does have one. Everything else stays shared - split a flow only after confirming the divergence per platform with `agent-device is visible "<selector>"`.
22+
1123
## Agent decision loop (interactive)
1224

1325
Before manually navigating, use this human-in-the-loop loop:
1426

1527
1. `agent-device snapshot -i` - see current state.
16-
2. `grep -H '^# @' .claude/skills/agent-device/flows/macros/*.ad` - interactive catalog.
28+
2. `grep -H '^# @' .claude/skills/agent-device/flows/macros/*.ad .claude/skills/agent-device/flows/macros/<platform>/*.ad` - interactive catalog. Where both list the same name, the platform copy wins.
1729
3. For each candidate flow, run `agent-device is exists "<selector>"` per `@pre`. Keep flows where every `@pre` passes.
1830
4. Rank survivors by goal closeness and present top macro candidates to the user with a short "why this flow" note:
1931
- Prefer flows whose `@post` selectors literally match destination language from the user request (same `text`, `label`, or selector phrase).
@@ -69,6 +81,7 @@ agent-device replay <flow>.ad -e EMAIL=other@example.com
6981
- **No `open`, no `close`, no `context` header.** Caller owns lifecycle.
7082
- **No fixed `wait` calls.** `fill`/`press` resolve selectors with retry. Only add `wait <selector>` for real post-action blocks.
7183
- **Durable selectors.** Prefer `id=...` first, then `role=... label=...`, with `||` fallbacks. Avoid `@eN` refs.
84+
- **Confirm every selector on the platform it is written for.** `snapshot -i` prints display tags, which are not selector values - a node printed as `[text-field]` may only match `role="textbox"`. Check the exit code of `agent-device is visible "<selector>"` before committing a selector, and never carry one across platforms unchecked.
7285
- **Every flow declares `@desc` and `@pre`.** Add `@post` for outcome-bearing flows; utility flows (for example `go-back`) may omit it. Add `@tag` when applicable.
7386
- **Choose directory intentionally.** Put reusable setup/navigation steps in `flows/macros/`; put outcome verification scenarios in `flows/tests/`.
7487
- **Keep scope coherent, not artificially tiny.** Flows can span multiple screens when that sequence is the reusable intent (for example "create and submit manual expense").

.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
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
context platform=android
2+
# @desc Complete onboarding with minimal choices (skip work email, pick "Something else" purpose, enter generic name). Lands on Home.
3+
# @pre text="What’s your work email?"
4+
# @post text="Home"
5+
# @post role="button" label="Search"
6+
# @tag onboarding
7+
# @param FIRST_NAME First name to enter on onboarding profile step.
8+
# @param LAST_NAME Last name to enter on onboarding profile step.
9+
10+
press "id=\"onboardingPrivateEmailSkipButton\" || role=\"button\" label=\"Skip\" || label=\"Skip\""
11+
press "role=\"button\" label=\"Something else\" || label=\"Something else\""
12+
fill "role=\"textfield\" label=\"First name\" editable=true || label=\"First name\" editable=true" "${FIRST_NAME}"
13+
fill "role=\"textfield\" label=\"Last name\" editable=true || label=\"Last name\" editable=true" "${LAST_NAME}"
14+
press "role=\"button\" label=\"Continue\" || label=\"Continue\""
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
context platform=android
2+
# @desc Send a chat message from inside an already-open chat. Reusable helper for setting up state in other flows. Does not navigate or open a chat - assumes the composer is visible. For the QA scenario that exercises the ManualSendMessage Sentry span, see flows/tests/send-message.ad.
3+
# @pre label="Write something..." editable=true
4+
# @post label="Write something..." editable=true
5+
# @tag chat
6+
# @param MESSAGE Message text to send in the currently open chat.
7+
8+
is exists "label=\"Write something...\" editable=true"
9+
fill "label=\"Write something...\" editable=true" "${MESSAGE}"
10+
press "role=\"button\" label=\"Send\" || label=\"Send\""
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
context platform=android
2+
# @desc Sign in with the shared agent-device test account. Supports both new-account and returning-account outcomes. Caller MUST randomize EMAIL via `-e EMAIL=agent-device-testing+<9digits>@gmail.com` to avoid account flagging.
3+
# @pre id="username"
4+
# @pre role="button" label="Continue"
5+
# @post role="button" label="Join" || role="button" label="Search"
6+
# @tag auth
7+
# @param EMAIL Login email. Use randomized alias format `agent-device-testing+<9digits>@gmail.com` to avoid account flagging.
8+
9+
fill "id=\"username\" || label=\"Phone or email\" editable=true" "${EMAIL}"
10+
press "role=\"button\" label=\"Continue\" || label=\"Continue\""
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
context platform=web
2+
# @desc Complete onboarding with minimal choices (skip work email, pick "Something else" purpose, enter generic name). Lands on Home.
3+
# @pre text="What’s your work email?"
4+
# @post text="Home"
5+
# @post role="button" label="Search"
6+
# @tag onboarding
7+
# @param FIRST_NAME First name to enter on onboarding profile step.
8+
# @param LAST_NAME Last name to enter on onboarding profile step.
9+
10+
press "role=\"button\" label=\"Skip\" || label=\"Skip\""
11+
press "role=\"button\" label=\"Something else\" || label=\"Something else\""
12+
fill "role=\"textbox\" label=\"First name\" || label=\"First name\"" "${FIRST_NAME}"
13+
fill "role=\"textbox\" label=\"Last name\" || label=\"Last name\"" "${LAST_NAME}"
14+
press "role=\"button\" label=\"Continue\" || label=\"Continue\""
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
context platform=web
2+
# @desc Send a chat message from inside an already-open chat. Reusable helper for setting up state in other flows. Does not navigate or open a chat - assumes the composer is visible. For the QA scenario that exercises the ManualSendMessage Sentry span, see flows/tests/send-message.ad.
3+
# @pre role="textbox" label="Write something..."
4+
# @post role="textbox" label="Write something..."
5+
# @tag chat
6+
# @param MESSAGE Message text to send in the currently open chat.
7+
8+
is exists "role=\"textbox\" label=\"Write something...\""
9+
fill "role=\"textbox\" label=\"Write something...\"" "${MESSAGE}"
10+
press "role=\"button\" label=\"Send\" || label=\"Send\""
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
context platform=web
2+
# @desc Sign in with the shared agent-device test account. Supports both new-account and returning-account outcomes. Caller MUST randomize EMAIL via `-e EMAIL=agent-device-testing+<9digits>@gmail.com` to avoid account flagging.
3+
# @pre role="textbox" label="Phone or email"
4+
# @pre role="button" label="Continue"
5+
# @post text="Welcome!" || text="Home"
6+
# @post role="button" label="Join" || role="button" label="Search"
7+
# @tag auth
8+
# @param EMAIL Login email. Use randomized alias format `agent-device-testing+<9digits>@gmail.com` to avoid account flagging.
9+
10+
fill "role=\"textbox\" label=\"Phone or email\" || label=\"Phone or email\"" "${EMAIL}"
11+
press "role=\"button\" label=\"Continue\" || label=\"Continue\""

.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

0 commit comments

Comments
 (0)