Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/devclient-picker-dismiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"rn-dev-agent-plugin": patch
"rn-dev-agent-cdp": patch
---

Add `cdp_dismiss_dev_client_picker` MCP tool (Android) and best-effort Dev
Client picker dismissal after Android deep links (#136 sub-3). Routed through a
single guarded `clearDevClientPickerIfPresent()` helper; iOS returns an
actionable manual-select message instead of touching the legacy agent-device
path. Cross-platform iOS support tracked as a follow-up.
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Fallback: `xcrun simctl` (iOS) + `adb` (Android) for device lifecycle (boot / in

### MCP Server (cdp-bridge)

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

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

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`.
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`.

iOS-only quirks worth knowing:
- `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.
Expand Down
5 changes: 2 additions & 3 deletions docs-site/src/content/docs/dev-client-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,18 @@ The standalone harness suites at `test-app/harness/suites/dev-client-picker.mjs`
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`).
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.
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).
- **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`.
- **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`.

## Fixed in this PR

_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).
- **`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).

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

- **#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`.
- 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.
- 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).
- **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.
- **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.
- **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.
- **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).
- **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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: "cdp_dismiss_dev_client_picker"
description: "Dismiss the Expo Dev Client \"Development servers\" picker on demand"
---

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.

## Parameters

| Name | Type | Required | Default | Constraints | Description |
|------|------|----------|---------|-------------|-------------|
| `platform` | `enum: ios | android` | No | | | Force platform. Otherwise resolved from the active session or the booted device. |

## Usage

```
cdp_dismiss_dev_client_picker()
```
Loading
Loading