Skip to content

Commit 68393fc

Browse files
Lykhoydaclaude
andauthored
docs(dev-client): coverage status + #136 sub-2 root-cause diagnosis (#183)
* docs(dev-client): add coverage-doc skeleton for harness-driven verification Empty 4-section skeleton (Works / Broken / Fixed / Deferred) populated by DC-Task 8's verify pass. References the standalone .mjs suites in the workspace repo that exercise dev-client-picker.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(dev-client): populate coverage doc with verify-pass findings DC-Task 8 verify pass. #136 sub-2 hang reproduced live (cdp_status ~33.5s with Dev Client picker up + Metro up); root cause is the 30s waitForReact in performSetup (setup.ts:67) against stale C++ targets that pass filterValidTargets while the picker blocks the bundle. Both harness suites re-run green (picker Metro-down 10ms no-hang; tutorial Metro-up). Fix deferred: no safe <=50 LOC change; proposed follow-up approach documented. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(dev-client): record CI deferral (DC-Task 9) + rationale in coverage doc CI deferred: the planned macos-15 harness workflow is (a) partly broken as drafted — the picker suite needs Metro DOWN, the tutorial suite needs Metro UP, but the workflow starts Metro once for both — and (b) low ROI today, since both suites are reproduction fixtures with weak assertions guarding a deferred-unfixed bug. Revisit when #136 sub-2 is fixed (CI then guards the fix; workflow must run picker Metro-DOWN then tutorial Metro-UP). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6e05a7a commit 68393fc

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: Dev Client picker + tutorial modal — Coverage Status
3+
description: What dev-client-picker.ts currently handles, what it doesn't, and which gaps are tracked for future work.
4+
---
5+
6+
# Dev Client picker + tutorial modal — Coverage Status
7+
8+
> **Last updated:** 2026-05-29 by PR #<PR_NUM> (filled in at PR-open time)
9+
10+
The standalone harness suites at `test-app/harness/suites/dev-client-picker.mjs` and `test-app/harness/suites/expo-tutorial-modal.mjs` (workspace repo) reproduce the Expo Dev Client server-picker and first-launch-tutorial states and exercise the handling code paths in `scripts/cdp-bridge/src/tools/dev-client-picker.ts`. This page enumerates what currently works, what's known-broken, what got fixed in this PR, and what's deferred to future PRs.
11+
12+
**Verify pass (DC-Task 8), 2026-05-29, UDID 78F7D2A1 / iOS 26.4, Node v24.13.0:**
13+
14+
| Suite | Metro | Result | Key datum |
15+
|---|---|---|---|
16+
| `dev-client-picker.mjs` | DOWN | `pass:true` (exit 0) | `cdp_status` returned in **10 ms** with "Metro not found" — Metro-down path does NOT hang |
17+
| `expo-tutorial-modal.mjs` | UP | `pass:true` (exit 0) | reproduces tutorial-eligible state (bundle loaded, 1 JS target); tutorial text CDP-invisible |
18+
| #136 sub-2 live repro | UP + picker up | **hang reproduced** | `cdp_status` took **33,481 ms** (~33.5 s) and then returned a misleading `ok:true` |
19+
20+
## What works (verified by harness)
21+
22+
- **Picker detection (when a device session exists).** `handleDevClientPicker()` and `isDevClientPickerShowing()` (`dev-client-picker.ts:144`, `:225`) detect the picker by `device_find`-matching the literal text `Development servers` / `DEVELOPMENT SERVERS` (`PICKER_INDICATORS`, `dev-client-picker.ts:45`). `parseFirstServerEntry()` (`:120`) then selects a server row to tap via a three-tier matcher: whole-line literal IP → `host:port` port-pattern (`:76`) → first non-header/non-footer row. Unit-test-covered via the `runAgentDevice` test seam.
23+
- **Auto-advance race handling.** `dismissPicker()` (`:169`) re-probes with `isDevClientPickerShowing()` before tapping and returns success if the single-server picker auto-advanced between detect and dispatch (`:176`), closing the ~30% Maestro "unable to find element" race from #136.
24+
- **Metro-DOWN fast path (no hang).** With Metro down, `cdp_status``autoConnect``discover()` short-circuits at Metro discovery (`discovery.ts:314`, `DISCOVERY_TIMEOUT_MS = 1500`) and returns "Metro not found" in ~10 ms. Verified live by the picker suite (`statusMs:10`). The #136 sub-2 hang is exclusively a Metro-UP phenomenon.
25+
- **CDP is non-perturbing to both surfaces.** `cdp_status` / `cdp_targets` / `cdp_connect` are pure Metro-WebSocket discovery + JS introspection — they never drive the device, so they never dismiss the picker or the tutorial (verified across repeated probes in both suites).
26+
27+
## What's broken (verified by harness)
28+
29+
- **#136 sub-2: `cdp_status` hangs ~33.5 s when the picker is up + Metro is up.** Reproduced live (DC-Task 8): `statusMs = 33,481`. Root cause is a chain, not a single line:
30+
1. The pre-`autoConnect` picker guard in `status.ts:128-132` calls `isDevClientPickerShowing()`, which is gated on `hasActiveSession()` (`dev-client-picker.ts:226`). On an MCP cold start there is no active device session, so the guard is a **no-op** and execution falls through to `autoConnect` (`status.ts:133`).
31+
2. With the picker up, Metro's `/json/list` still advertises **stale C++ targets** from the prior bundle session (`React Native Bridgeless [C++ connection]`, `Reanimated UI runtime [C++ connection]`, `vm:None`). These pass `filterValidTargets` because their `title`/`description` include "React Native" (`discovery.ts:44-47`), so `discover()` does NOT throw the fast "No Hermes debug target found" (`discovery.ts:332`).
32+
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`).
33+
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.
34+
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`.
36+
- **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`.
37+
38+
## Fixed in this PR
39+
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).
41+
42+
## Deferred (>50 LOC or new code path)
43+
44+
- **#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`.
45+
- 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.
46+
- 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).
47+
- **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.
49+
- **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).
50+
- **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.

0 commit comments

Comments
 (0)