Skip to content

Commit f98111e

Browse files
authored
feat(help): document behavioral guarantees in help workflow (#1051) (#1054)
Add a "Guarantees" section to `agent-device help workflow`: statements of fact for agents to reason from instead of probing behavior with trial commands. Covers selector ambiguity (folds/extends the existing #1040 paragraph instead of duplicating it), hittability/targetHittable, open idempotent-foreground vs --relaunch's single simctl launch --terminate-running-process call, close's runner retention policy plus the AGENT_DEVICE_IOS_RUNNER_IDLE_STOP_MS idle-stop window, ref lifetime (cleared by open/relaunch), diff snapshot's comparison baseline, and wait's polling model. Each statement is traceable to source, noted in the PR body. Adds three doc-assertion tests in cli-help.test.ts mirroring the #1040 pattern.
1 parent 2adf3d5 commit f98111e

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

‎src/__tests__/cli-help.test.ts‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,58 @@ test('help workflow documents the selector disambiguation policy (#1037)', async
118118
assert.match(result.stdout, /targetHittable: false/);
119119
});
120120

121+
test('help workflow documents selector and hittability guarantees (#1051)', async () => {
122+
const result = await runCliCapture(['help', 'workflow']);
123+
assert.equal(result.code, 0);
124+
assert.equal(result.calls.length, 0);
125+
assert.match(result.stdout, /Guarantees:/);
126+
assert.match(
127+
result.stdout,
128+
/auto-disambiguates deepest node first, then smallest on-screen area/,
129+
);
130+
assert.match(result.stdout, /Selector did not resolve uniquely/);
131+
assert.match(result.stdout, /non-hittable resolution is allowed by design/);
132+
assert.match(result.stdout, /targetHittable: false plus a hint/);
133+
});
134+
135+
test('help workflow documents open/close/relaunch runner guarantees (#1051)', async () => {
136+
const result = await runCliCapture(['help', 'workflow']);
137+
assert.equal(result.code, 0);
138+
assert.equal(result.calls.length, 0);
139+
assert.match(result.stdout, /idempotent-foreground for an already-running app/);
140+
assert.match(
141+
result.stdout,
142+
/one simctl launch --terminate-running-process call instead of a separate terminate-then-launch/,
143+
);
144+
assert.match(
145+
result.stdout,
146+
/keeps a healthy iOS simulator XCTest runner warm by default so the next open on that device skips the runner build/,
147+
);
148+
assert.match(result.stdout, /the session held a device lease/);
149+
assert.match(result.stdout, /AGENT_DEVICE_IOS_RUNNER_IDLE_STOP_MS/);
150+
assert.match(result.stdout, /default 5 minutes/);
151+
});
152+
153+
test('help workflow documents ref lifetime, snapshot diff, and wait guarantees (#1051)', async () => {
154+
const result = await runCliCapture(['help', 'workflow']);
155+
assert.equal(result.code, 0);
156+
assert.equal(result.calls.length, 0);
157+
assert.match(
158+
result.stdout,
159+
/open and open --relaunch clear the session's stored snapshot outright/,
160+
);
161+
assert.match(
162+
result.stdout,
163+
/diff snapshot compares the current capture against the session's last stored snapshot/,
164+
);
165+
assert.match(
166+
result.stdout,
167+
/initializes the baseline and reports zero additions\/removals instead of failing/,
168+
);
169+
assert.match(result.stdout, /polls on a fixed interval \(300ms\)/);
170+
assert.match(result.stdout, /Timing out raises a command failure/);
171+
});
172+
121173
test('help unknown command prints error plus global usage and skips daemon dispatch', async () => {
122174
const result = await runCliCapture(['help', 'not-a-command']);
123175
assert.equal(result.code, 1);

‎src/cli/parser/cli-help.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,16 @@ React Native dev loop:
321321
If apps lookup misses the project but shows Expo Go/dev-client and a project URL is available, open the URL/host shell; if no URL is available, ask instead of inventing an app id.
322322
Expo Dev Client/development builds: open the installed dev-client app id/name; if a dev-client URL is provided, open that URL next. For Metro setup use metro prepare --kind expo.
323323
324+
Guarantees:
325+
Statements of fact for agents to reason from without probing them via trial commands. Each is backed by source in the agent-device repo; behavior changes land with an updated statement here.
326+
Selector ambiguity: a selector on an interactive command that matches multiple elements does not fail by default. Resolution auto-disambiguates deepest node first, then smallest on-screen area; only an exact tie on both fails with "Selector did not resolve uniquely (...)". replay and replay-heal apply the same depth-then-area policy, so recorded and live commands pick the same candidate.
327+
Hittability: iOS AX hittable:false on a resolved node does not block resolution or fail the command; non-hittable resolution is allowed by design because iOS AX hittable flags are unreliable on deep React Native trees. press/fill/click success responses carry targetHittable: false plus a hint when the resolved ref or selector target reports hittable: false, so treat that as a signal to verify with a snapshot or re-target, not as a failure.
328+
Open: on iOS, open <app> without --relaunch dispatches a plain simctl launch, which is idempotent-foreground for an already-running app (it brings the process forward; it does not restart it). open --relaunch restarts the app; on iOS simulators (not real devices or macOS) this collapses to one simctl launch --terminate-running-process call instead of a separate terminate-then-launch, so relaunch is a single step there.
329+
Close and runner retention: close keeps a healthy iOS simulator XCTest runner warm by default so the next open on that device skips the runner build, unless --shutdown was requested, the session was recording, the session held a device lease, or the device used a scoped (non-default) simulator set — any of those tear the runner down on close. A retained runner auto-stops after an idle window (default 5 minutes) to release the device's runner lease for other daemons; set AGENT_DEVICE_IOS_RUNNER_IDLE_STOP_MS to override the window, or 0 to disable idle stop and retain until daemon exit.
330+
Ref lifetime: refs from a snapshot/snapshot -i are only valid until the next state-changing command. open and open --relaunch clear the session's stored snapshot outright, so every ref from before an open/relaunch is invalid; press/fill/click/scroll/back and similar mutations invalidate refs from prior turns even though the session snapshot itself is refreshed internally by those commands.
331+
Snapshot diff: diff snapshot compares the current capture against the session's last stored snapshot (from any prior snapshot, snapshot -i, or diff snapshot call), not a fixed baseline from session start. If no prior snapshot exists yet, diff snapshot initializes the baseline and reports zero additions/removals instead of failing.
332+
Wait: wait text|selector|@ref polls on a fixed interval (300ms) up to a timeout (10s default, override with the trailing timeoutMs positional) by re-capturing state each poll; it does not push/subscribe. Timing out raises a command failure rather than returning a not-found result.
333+
324334
Escalate:
325335
help debugging logs, network, alerts, traces, flaky runtime failures
326336
help react-devtools React Native performance, profiling, props/state/hooks, slow renders, rerenders

0 commit comments

Comments
 (0)