Skip to content

Commit c4804dc

Browse files
Lykhoydaclaude
andauthored
fix(#136 sub-3): cdp_dismiss_dev_client_picker + Android deep-link auto-dismiss (#196)
* docs(#136): design spec for dev-client picker dismiss tool + launch wiring (sub-3) Approach B: cdp_dismiss_dev_client_picker MCP tool (Android-as-is) + iOS guard + best-effort device_deeplink dismissal, routed through one shared helper. Sub-1/sub-2 already shipped (#174/#187); this covers the remaining sub-3. * docs(#136): implementation plan for dev-client picker dismiss (sub-3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#136): add guarded clearDevClientPickerIfPresent seam (sub-3) Wraps the unchanged Android handleDevClientPicker(); iOS short-circuits with an actionable message and never touches the legacy agent-device path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#136): map picker outcome to ToolResult in dismiss handler (sub-3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#136): register cdp_dismiss_dev_client_picker MCP tool (sub-3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#136): best-effort Dev Client picker dismiss after Android deep links (sub-3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(#136): document cdp_dismiss_dev_client_picker; bump tool count 75->76 (sub-3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#136): add changeset (patch) for dev-client picker dismiss tool Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * build(#136): compile dist for dev-client picker dismiss tool (sub-3) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5c4ca04 commit c4804dc

14 files changed

Lines changed: 1204 additions & 10 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"rn-dev-agent-plugin": patch
3+
"rn-dev-agent-cdp": patch
4+
---
5+
6+
Add `cdp_dismiss_dev_client_picker` MCP tool (Android) and best-effort Dev
7+
Client picker dismissal after Android deep links (#136 sub-3). Routed through a
8+
single guarded `clearDevClientPickerIfPresent()` helper; iOS returns an
9+
actionable manual-select message instead of touching the legacy agent-device
10+
path. Cross-platform iOS support tracked as a follow-up.

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Fallback: `xcrun simctl` (iOS) + `adb` (Android) for device lifecycle (boot / in
124124

125125
### MCP Server (cdp-bridge)
126126

127-
**75 tools** exposed via MCP (re-audited 2026-05-29; counted from `trackedTool()` calls in `scripts/cdp-bridge/src/index.ts`). Five conceptual families:
127+
**76 tools** exposed via MCP (re-audited 2026-05-31; counted from `trackedTool()` calls in `scripts/cdp-bridge/src/index.ts`). Five conceptual families:
128128

129129
**CDP tools** — React internals via Chrome DevTools Protocol over WebSocket:
130130
- `cdp_status` — health check with domain capabilities + reconnect state
@@ -149,7 +149,7 @@ Fallback: `xcrun simctl` (iOS) + `adb` (Android) for device lifecycle (boot / in
149149
- `device_scrollintoview` / `device_back` / `device_longpress` / `device_pinch`
150150
- `device_permission` / `device_batch`
151151

152-
Plus device helpers filed alongside CDP in code: `device_deeplink`, `device_accept_system_dialog`, `device_dismiss_system_dialog`, `device_focus_next`, `device_pick_date`, `device_pick_value`, `device_record`, `device_reset_state`.
152+
Plus device helpers filed alongside CDP in code: `device_deeplink`, `device_accept_system_dialog`, `device_dismiss_system_dialog`, `device_focus_next`, `device_pick_date`, `device_pick_value`, `device_record`, `device_reset_state`, `cdp_dismiss_dev_client_picker`.
153153

154154
iOS-only quirks worth knowing:
155155
- `device_fill` may surface a Swift-internal `XCUIElement.typeText` quiescence-timeout from XCTest's main-thread sync. The TS client treats this specific error as success on `.type` (`meta.runnerTimeoutShim: true`) because the side-effect (text appended to the field) demonstrably succeeds — observed across the iOS-MVP smoke-tests.

docs-site/src/content/docs/dev-client-coverage.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,18 @@ The standalone harness suites at `test-app/harness/suites/dev-client-picker.mjs`
3232
3. `connectToTarget` (`connect.ts:215`) opens the WebSocket and the `Runtime.evaluate('1+1')` pre-flight probe **passes** (the C++ connection answers even with no live JS app context), so it proceeds to `ctx.setup()` (`connect.ts:254`).
3333
4. **Dominant cost:** `performSetup` calls `waitForReact(evaluate, REACT_READY_TIMEOUT_MS)` (`setup.ts:67`, `REACT_READY_TIMEOUT_MS = 30_000`). The picker is blocking the bundle, so React never becomes ready and the poll loop (`setup.ts:174-192`) burns its **full 30 s** before logging `React not ready after 30000ms — helpers will be injected anyway` and continuing. ~3.5 s of discovery/connect/probe overhead on top yields the observed ~33.5 s.
3434
5. **Worse than slow:** the call ultimately returns `ok:true` (`status.app.dev:true`) rather than surfacing that the picker is blocking the bundle — the user gets a misleading slow "success", not an actionable message. Exposed by the Metro-UP live repro (the `dev-client-picker.mjs` suite runs Metro-DOWN by design and records `statusHung:false` honestly).
35-
- **No MCP-exposed picker-dismiss tool.** `handleDevClientPicker()` is internal-only — invoked from `status.ts` (`:130`, `:240`), `device-reset-state.ts`, and `startup-replay.ts`, never registered as an MCP tool. An agent cannot dismiss the picker on demand; the only path is the device-session-gated, iOS-perturbing internal call. Both suites record `dismissTool:null` / `handlerToolName:null`.
3635
- **Tutorial modal is CDP-invisible and unhandled.** The expo-dev-menu first-launch tutorial ("This is the developer menu…" + Continue) renders in expo-dev-menu's **separate native RN surface**, not the app's Hermes context. `__RN_AGENT.getTree()` and `cdp_component_tree` return only the app tree (no "developer menu"/"Continue"); `device_snapshot` yields no a11y tree in this env (global `agent-device` daemon intercepts `action=open`). There is **no non-perturbing programmatic detector** for the tutorial text and **no dismiss handler**`handleDevClientPicker()` targets the SERVER picker only. Verified by `expo-tutorial-modal.mjs`.
3736

3837
## Fixed in this PR
3938

40-
_None._ The #136 sub-2 hang root cause was diagnosed and reproduced live, but a correct fix exceeds the in-scope budget — see Deferred. No shipped code was changed in this PR (coverage doc only).
39+
- **`cdp_dismiss_dev_client_picker` (Android) — on-demand picker dismissal.** New MCP tool wrapping `handleDevClientPicker()` through the guarded `clearDevClientPickerIfPresent()` seam (`tools/dev-client-picker.ts`). `device_deeplink` now best-effort dismisses on Android success. iOS is guarded with an actionable manual-select message (never invokes the legacy `agent-device find` path). Cross-platform iOS re-path remains deferred (see Deferred).
4140

4241
## Deferred (>50 LOC or new code path)
4342

4443
- **#136 sub-2 hang — fast-fail / clear message when the picker blocks the bundle (Metro-UP).** Deferred: no safe ≤50 LOC fix exists in `dev-client-picker.ts`/`status.ts`.
4544
- The dominant cost is the 30 s `waitForReact` in `performSetup` (`setup.ts:67`), which is shared by every connect path (cold start, reload, soft-reconnect). Shortening it globally would regress legitimate slow first-builds. Threading a status-specific shorter timeout down through `autoConnect → discoverAndConnect → connectToTarget → setup() → performSetup` touches 4-5 files plus the `ConnectContext` interface — well over 50 LOC of shared connect plumbing.
4645
- A pre-`autoConnect` picker detector based on target shape is **not reliable**: live experiment (DC-Task 8) showed `/json/list` returns 0 Hermes targets *transiently during every normal bundle reload*, not only when the picker is up — and the stale C++ targets pass `filterValidTargets`. Such a heuristic would false-positive on healthy connects (the false-positive risk the code-quality review already flagged on the `pickerDetected` gate).
4746
- **Proposed follow-up approach (for the issue):** in `connectToTarget`, after the `1+1` pre-flight probe succeeds but before `setup()`, run a short bounded "is React reachable?" probe (e.g. `waitForReact` with a ~3-5 s budget on the *status-initiated* connect only) and, on timeout against a target whose `vm` is not `Hermes`, abort that candidate with a typed `PickerBlockingBundleError`; `status.ts` maps it to a fast `failResult`/`warnResult` like *"Dev Client picker is blocking the bundle — select your Metro server on the simulator, then retry cdp_status."* Needs a new error type + a status-vs-other connect-intent flag threaded through the connect chain; estimated >50 LOC across `connect.ts`/`setup.ts`/`status.ts`/`cdp-client.ts`. Track as a dedicated #136 sub-2 follow-up issue.
48-
- **No MCP-exposed picker-dismiss tool.** Deferred: registering a new `cdp_dismiss_dev_client_picker` MCP tool is a new code path (tool schema + `trackedTool` registration + iOS-perturbation handling). Track as a follow-up if agents need on-demand dismissal beyond the internal `cdp_status`/`device_reset_state`/`startup-replay` call sites.
47+
- **Cross-platform `cdp_dismiss_dev_client_picker` iOS re-path.** The Android path is shipped (PR #136 sub-3). iOS returns an actionable manual-select message today (no legacy `agent-device find` call). A native iOS dismiss (XCTest-based, via `rn-fast-runner`) needs ~50+ LOC of new device verb + error-typing — track as a follow-up.
4948
- **Tutorial-modal detection + dismissal.** Deferred: requires either OCR over `xcrun simctl io screenshot` (no programmatic CDP/a11y signal exists for expo-dev-menu's separate surface) or a non-perturbing native bridge into expo-dev-menu — both are new code paths well beyond the budget. Track as a follow-up (relates to #173 sub-4).
5049
- **CI workflow for the harness suites (planned DC-Task 9).** Deferred: the planned `macos-15` workflow runs a ~20 min full native build (`expo prebuild` + `expo run:ios`) + Metro to drive both suites, but (a) the two suites have **opposite Metro requirements** — the picker suite needs Metro DOWN, the tutorial suite needs Metro UP — which the single-Metro-start workflow draft does not satisfy, and (b) both suites are reproduction fixtures with intentionally weak assertions (see "documents the gap" above) guarding a bug that is itself deferred-unfixed. A heavy, flaky-prone CI job guarding weak-signal reproduction of an unfixed bug is low ROI today. **Revisit when #136 sub-2 is fixed** — at that point CI can guard the fix (an asserting suite), and the workflow must run the picker suite Metro-DOWN, then start Metro, then run the tutorial suite Metro-UP. Suites remain runnable on-demand locally meanwhile.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
title: "cdp_dismiss_dev_client_picker"
3+
description: "Dismiss the Expo Dev Client \"Development servers\" picker on demand"
4+
---
5+
6+
Dismiss the Expo Dev Client "Development servers" picker on demand. The picker is a native expo-dev-menu screen that blocks the JS bundle after deep links, restarts, permission changes, or clearState; this taps the configured Metro server entry so CDP/the bundle can proceed. Android only today (requires an open device session — call device_snapshot action="open" first). iOS returns an actionable manual-select message (cross-platform support tracked as a follow-up). Prefer this over a racy Maestro `runFlow when: visible: "DEVELOPMENT SERVERS"` block.
7+
8+
## Parameters
9+
10+
| Name | Type | Required | Default | Constraints | Description |
11+
|------|------|----------|---------|-------------|-------------|
12+
| `platform` | `enum: ios | android` | No | | | Force platform. Otherwise resolved from the active session or the booted device. |
13+
14+
## Usage
15+
16+
```
17+
cdp_dismiss_dev_client_picker()
18+
```

0 commit comments

Comments
 (0)