Skip to content

Commit 4a0b183

Browse files
committed
docs(cli): advertise open --foreground and snapshot --actions in the workflow card
open --foreground (#1670/#1671) and snapshot -i --actions (#1665) shipped with no mention in the compact `help workflow` card, so a planning model never discovers either. Add one terse line each: the foreground fast-path in Bootstrap, and the merged-element custom-action guidance in Validation and evidence. Stays under the 9,000-byte compact-card budget (8493 -> 8908 bytes). Adds two help-conformance bench cases per the repo's changed-guidance rule: foreground-attach-single-sim (correct plan starts with `open --foreground` in an unambiguous single-sim scenario, fail-closed alternative forbidden) and merged-card-actions-not-directly-invokable (a merged Bluesky-style feed card's actions list is evidence, not a selector). Both use a real pinned sample rebuilt through the production snapshot renderer.
1 parent ac9e4d0 commit 4a0b183

6 files changed

Lines changed: 139 additions & 0 deletions

File tree

scripts/__tests__/help-conformance-sample-producers.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
APP_NOT_INSTALLED_SAMPLE,
55
BROWSERSTACK_CONNECT_SAMPLE,
66
DEVICE_IN_USE_SAMPLE,
7+
MERGED_CARD_ACTIONS_SAMPLE,
78
NOT_SETTLED_SAMPLE,
89
OFFSCREEN_TARGET_SNAPSHOT_SAMPLE,
910
PRIVATE_AX_RECOVERY_SAMPLE,
@@ -267,6 +268,54 @@ export const SAMPLE_PRODUCERS: SampleProducer[] = [
267268
).trimEnd();
268269
},
269270
},
271+
{
272+
name: 'MERGED_CARD_ACTIONS_SAMPLE',
273+
producer: "the snapshot renderer with --actions naming a merged element's custom actions",
274+
sample: MERGED_CARD_ACTIONS_SAMPLE,
275+
render: () => {
276+
// A Bluesky-style feed item merged into one Link node: its Reply/Repost/
277+
// menu controls are AX custom actions, not child nodes, so they only
278+
// surface when --actions is passed through to the renderer.
279+
const nodes = [
280+
{
281+
index: 0,
282+
ref: 'e1',
283+
type: 'Application',
284+
label: 'Bluesky',
285+
rect: { x: 0, y: 0, width: 390, height: 844 },
286+
},
287+
{
288+
index: 1,
289+
ref: 'e2',
290+
parentIndex: 0,
291+
type: 'Window',
292+
rect: { x: 0, y: 0, width: 390, height: 844 },
293+
},
294+
{
295+
index: 2,
296+
ref: 'e3',
297+
parentIndex: 1,
298+
type: 'CollectionView',
299+
interactive: true,
300+
rect: { x: 0, y: 60, width: 390, height: 700 },
301+
},
302+
{
303+
index: 3,
304+
ref: 'e72',
305+
parentIndex: 2,
306+
type: 'Link',
307+
label: 'feedItem-by-whiskers.test',
308+
interactive: true,
309+
rect: { x: 0, y: 60, width: 390, height: 140 },
310+
actions: ['Reply', 'Repost', 'Open post options menu'],
311+
},
312+
];
313+
return formatSnapshotText(
314+
{ nodes, backend: 'xctest', truncated: false },
315+
{ interactiveOnly: true },
316+
).trimEnd();
317+
},
318+
},
270319
{
271320
name: 'DEVICE_IN_USE_SAMPLE',
272321
producer: 'the real session-open by-session conflict producer',

scripts/help-conformance-cases.mjs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
APP_NOT_INSTALLED_SAMPLE,
44
BROWSERSTACK_CONNECT_SAMPLE,
55
DEVICE_IN_USE_SAMPLE,
6+
MERGED_CARD_ACTIONS_SAMPLE,
67
NOT_SETTLED_SAMPLE,
78
OFFSCREEN_TARGET_SNAPSHOT_SAMPLE,
89
SETTLE_DIFF_SAMPLE,
@@ -642,4 +643,53 @@ Use the output already shown to determine whether the feed-search UI is present,
642643
{ id: 'noRedundantInstall', pattern: /(?:^|\n)agent-device\s+install\b/i },
643644
],
644645
},
646+
{
647+
id: 'foreground-attach-single-sim',
648+
docs: ['--help:first30', 'workflow'],
649+
task: 'You are starting fresh with no active session. The environment guarantees exactly one booted iOS simulator with exactly one app running on it -- the app you want to keep testing. Plan the command to attach to it and get its initial interactive snapshot in a single call (this only resolves unambiguously because of that guarantee, and it rejects an explicit app or device selector), then press the visible Continue control and close the session.',
650+
expectations: ['validPlanCommands', 'fullPrefix', 'usesSettleOnMutations', 'opensAndCloses'],
651+
matchers: [
652+
{
653+
id: 'startsWithForegroundOpen',
654+
pattern: /^agent-device\s+open\s+--foreground\b/i,
655+
},
656+
{
657+
id: 'pressesContinueAfterAttach',
658+
pattern: /agent-device\s+press\s+[^\n]*continue[^\n]*--settle\b/i,
659+
},
660+
],
661+
forbidden: [
662+
{
663+
id: 'noAppPositionalWithForeground',
664+
pattern: /open[ \t]+(?!--)\S+[^\n]*--foreground\b|open[ \t]+--foreground[ \t]+(?!--)\S+/i,
665+
},
666+
{
667+
id: 'noDeviceSelectorWithForeground',
668+
pattern: /--foreground\b[^\n]*--(?:udid|device)\b|--(?:udid|device)\b[^\n]*--foreground\b/i,
669+
},
670+
{ id: 'noRawCoordinateTarget', pattern: RAW_COORDINATE_TARGET },
671+
],
672+
},
673+
{
674+
id: 'merged-card-actions-not-directly-invokable',
675+
docs: ['--help:first30', 'workflow'],
676+
task: quiz(
677+
MERGED_CARD_ACTIONS_SAMPLE,
678+
'The goal is to reply to this post. The actions list names "Reply" as a hidden affordance on @e72, but that name is not a pressable selector. What command should run next?',
679+
),
680+
expectations: ['validPlanCommands', 'fullPrefix'],
681+
matchers: [
682+
{
683+
id: 'opensCardToReachReply',
684+
pattern: /(?:^|\n)agent-device\s+(?:press|click)\s+@e72\b[^\n]*--settle\b/i,
685+
},
686+
],
687+
forbidden: [
688+
{
689+
id: 'noPressingActionNameAsSelector',
690+
pattern: /(?:^|\n)agent-device\s+(?:press|click|find)\b[^\n]*(?:label|text)="?reply"?/i,
691+
},
692+
{ id: 'noRawCoordinateTarget', pattern: RAW_COORDINATE_TARGET },
693+
],
694+
},
645695
];

scripts/help-conformance-sample-outputs.mjs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,19 @@ Next:
147147
Use the installed package or bundle identifier in open, not the app artifact name.
148148
After close, run agent-device artifacts --json --session adc-browserstack for provider video and logs.`,
149149
};
150+
151+
// Merged feed-item card on iOS (#1665): the row itself is the only ref — its
152+
// Reply/Repost/menu controls have no separate child nodes in the tree, so
153+
// snapshot -i alone would show a plain link with no way to act on it.
154+
// snapshot -i --actions names the hidden affordances instead of hiding them
155+
// silently; the names are evidence only, never directly invokable (help
156+
// workflow: "reach via its detail screen, labeled children elsewhere, or
157+
// coordinates").
158+
export const MERGED_CARD_ACTIONS_SAMPLE = {
159+
command: 'agent-device snapshot -i --actions',
160+
output: `Snapshot: 4 nodes
161+
@e1 [application] "Bluesky"
162+
@e2 [window]
163+
@e3 [collection]
164+
@e72 [link] "feedItem-by-whiskers.test" actions: ["Reply", "Repost", "Open post options menu"]`,
165+
};

src/__tests__/cli-help.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,20 @@ test('help workflow documents open/close/relaunch runner guarantees as lifecycle
135135
assert.match(result.stdout, /Env vars: help physical-device/);
136136
});
137137

138+
test('help workflow advertises open --foreground and snapshot -i --actions', async () => {
139+
const result = await runCliCapture(['help', 'workflow']);
140+
assert.equal(result.code, 0);
141+
assert.equal(result.calls.length, 0);
142+
assert.match(
143+
result.stdout,
144+
/No session, one booted iOS simulator, one running app: open --foreground attaches and returns the initial snapshot in one call; fails closed \(AMBIGUOUS_MATCH\) otherwise; rejects an explicit app\/--udid\/--device/,
145+
);
146+
assert.match(
147+
result.stdout,
148+
/snapshot -i --actions names custom-action affordances hidden inside a merged element \(iOS sim only\); not directly invokable -- reach via its detail screen, labeled children elsewhere, or coordinates/,
149+
);
150+
});
151+
138152
test('help physical-device documents the runner/daemon lifecycle detail moved out of workflow (#1051)', async () => {
139153
const result = await runCliCapture(['help', 'physical-device']);
140154
assert.equal(result.code, 0);

src/cli/parser/__tests__/cli-help-topics.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ test('usageForCommand resolves workflow help topic', async () => {
253253
);
254254
assert.match(help, /Known flow: batch \.\/steps\.json \(help scripting\)/);
255255
assert.match(help, /Shapes and platform quirks: help gestures/);
256+
assert.match(
257+
help,
258+
/No session, one booted iOS simulator, one running app: open --foreground attaches and returns the initial snapshot in one call; fails closed \(AMBIGUOUS_MATCH\) otherwise; rejects an explicit app\/--udid\/--device/,
259+
);
256260
assert.match(help, /Never open artifact paths or invent package ids/);
257261
assert.match(
258262
help,
@@ -302,6 +306,10 @@ test('usageForCommand resolves workflow help topic', async () => {
302306
/confirm the requested end state is actually visible on the current screen, scrolling it into view if needed/,
303307
);
304308
assert.match(help, /get text alone, or stopping one screen early, is not enough/);
309+
assert.match(
310+
help,
311+
/snapshot -i --actions names custom-action affordances hidden inside a merged element \(iOS sim only\); not directly invokable -- reach via its detail screen, labeled children elsewhere, or coordinates/,
312+
);
305313
assert.match(help, /Perf\/memory\/log\/network\/trace\/crash: help debugging/);
306314
assert.match(help, /Recording, save-script, batch, replay repair: help scripting/);
307315
assert.match(help, /help react-native for Metro\/Re\.Pack reload/);

src/cli/parser/cli-help.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ Command shape:
226226
Bootstrap:
227227
agent-device devices --platform ios
228228
agent-device open MyApp --platform ios --device "iPhone 17 Pro"
229+
No session, one booted iOS simulator, one running app: open --foreground attaches and returns the initial snapshot in one call; fails closed (AMBIGUOUS_MATCH) otherwise; rejects an explicit app/--udid/--device.
229230
Install arguments are app/package id then artifact path: agent-device install com.example.app ./dist/app.apk --platform android, then open <id> --relaunch for fresh state. Use reinstall only when explicitly requested.
230231
Unknown app id: devices, then apps, then open <discovered-app-id>. Never open artifact paths or invent package ids; ask if lookup misses the target.
231232
Apple CI: prepare ios-runner after boot/install, before replay/test (help prepare). Remote/cloud: connect -> open -> commands -> close -> disconnect (help remote). Reusable scripts, secret-safe fills, replay repair: help scripting.
@@ -264,6 +265,7 @@ Validation and evidence:
264265
Nearby mutation diff: diff snapshot -i; with no prior snapshot it initializes the baseline (zero changes) instead of failing.
265266
Named expectations need the exact text/selector via wait/is/get/find -- a bare screenshot/snapshot is not verification. Before declaring a task done, confirm the requested end state is actually visible on the current screen, scrolling it into view if needed; get text alone, or stopping one screen early, is not enough.
266267
When an action only reveals or reaches a target, verify the exact target named, not just the action. Prefer testIDs/ids/selectors over visible text. Icon/tappable proof: screenshot --overlay-refs; if snapshot is sparse/AX-unavailable, use plain screenshot and coordinates, then retry snapshot -i on another screen.
268+
snapshot -i --actions names custom-action affordances hidden inside a merged element (iOS sim only); not directly invokable -- reach via its detail screen, labeled children elsewhere, or coordinates.
267269
Perf/memory/log/network/trace/crash: help debugging. Recording, save-script, batch, replay repair: help scripting.
268270
269271
React Native: help react-native for Metro/Re.Pack reload, DevTools, RN overlays. JS-only change: metro reload, find "Home"; open --relaunch for native reset.

0 commit comments

Comments
 (0)