Skip to content

iOS setup and app launch from the tool itself: doctor --fix, launch --arg, a PATH-aware plugin launcher - #14

Merged
eiliya-luzia merged 2 commits into
mainfrom
feat/ios-runtime-bootstrap-and-ergonomics
Sep 25, 2026
Merged

eiliya-luzia merged 2 commits into
mainfrom
feat/ios-runtime-bootstrap-and-ergonomics

Conversation

@matibzurovski

Copy link
Copy Markdown
Contributor

Why

Verifying an iOS build through the Claude Code plugin took a third of a session before the first analyser command ran, then a manual simctl launch plus a guessed sleep on every relaunch. Each obstacle was small; together they were most of the time. The session that hit them is written up here: https://grim-seal.drophere.cc/ (an iOS hub verification on two simulators).

  1. The plugin was installed but could not start: uv was absent, and once installed to /opt/homebrew/bin the MCP host's PATH did not list it, so the plugin reported only the host's bare command not found.
  2. brew install axe failed on a fresh machine because a current Homebrew refuses to load cameroncooke/axe until it is trusted; the README's one-liner had no brew trust step, and aua --platform ios doctor could only say "axe missing".
  3. A test build reads its launch flags from argv (--uitesting, feature-flag overrides). aua app launch had no way to pass them, so every relaunch bypassed the tool.
  4. Element bounds on iOS include a tile's blurred rim and artwork bleeding past a card's corner, which read as an overflow when the check was exactly about margins.

What

  • aua doctor --fix installs the host tooling a platform can install. On iOS that is AXe through Homebrew — tap, brew trust (tolerated when an older Homebrew lacks it), install — with each step reported. ./install.sh --with-ios does the same at setup, and the by-hand hint carries all three steps. Host tooling only, so no device-ledger entry; a failed install is the one thing doctor exits non-zero on.
  • aua app launch <bundle> --arg ARG (repeatable) hands a simulator app its process arguments through simctl launch; MCP app / app_launch_and_analyze take arguments. Android has no process arguments and refuses them with a hint rather than dropping them. The engine passes the list only when given, so a runtime without the parameter is untouched. Combined with the existing global --until rid:<landing>, a relaunch is one call with arrival evidence.
  • Plugin MCP launcher. .mcp.json starts the server through sh -c: it prepends ~/.local/bin, /opt/homebrew/bin, /usr/local/bin and ~/.cargo/bin, and says what to install when uvx is nowhere. The pinned release moves to env.AUA_SPEC; the version test and bump-version.sh read it there. claude plugin validate . passes.
  • docs/ios.md explains that bounds are accessibility frames and what to measure for a pixel-exact layout check.

Not in this PR

Two items from the same session are left as suggestions:

  • Switching the lease inside an ordinary --serial command. I implemented --switch-lease and pulled it: a target acquired by replacement is a reservation that validate_use refuses until lease acquire --replace completes the handoff, so doing it inline means reworking that two-phase design, which deserves its own change.
  • An action-bound --until that waits on "the tree changed" (a tab switch has no positive term to name), and a shorter post-action stability confirmation on simulators, where a satisfied --until still spent the 5s ceiling confirming.

Tests

New: iOS doctor-fix (installs, tolerates a Homebrew without trust, reports a failed install, leaves an installed AXe alone), launch arguments reaching simctl in order, engine passing arguments through a fake device, and the CLI's doctor --fix rendering and failure path. The version-consistency tests follow the launcher. Lint, mypy and the touched suites are green locally.

🤖 Generated with Claude Code

Verifying an iOS build with the plugin took a third of a session before
the first command ran: uv was absent, AXe's tap had to be trusted before
Homebrew would install it, and once both were there the MCP host's PATH
still did not list where they had gone. Then every relaunch of a test
build with launch flags went through simctl by hand, followed by a
guessed sleep.

- `aua doctor --fix` installs AXe (tap, `brew trust`, install) when the
  iOS adapter finds it missing, reporting each step; the by-hand hint
  and `./install.sh --with-ios` carry the same three steps. Host tooling
  only: no target is touched, so no device-ledger entry.
- `aua app launch <bundle> --arg ARG` (repeatable) hands a simulator app
  its process arguments through `simctl launch`; MCP takes `arguments`.
  Android has no process arguments and refuses them rather than dropping
  them. The engine passes the list through only when given, so a runtime
  without the parameter is untouched.
- The plugins' MCP server starts through a `sh -c` launcher that looks
  for `uvx` in `~/.local/bin`, `/opt/homebrew/bin`, `/usr/local/bin` and
  `~/.cargo/bin` before the host's PATH, and says what to install when it
  is nowhere. The pinned release moves to `env.AUA_SPEC`; the version
  test and `bump-version.sh` read it there.
- `docs/ios.md` says that element bounds are accessibility frames, which
  a rim or artwork past the view's edge widens by a few points.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@matibzurovski

Copy link
Copy Markdown
Contributor Author

Full-suite comparison on this machine (macOS, Python 3.11, xdist): main and this branch fail the same 82 tests — 76 in test_the_system_one_navigator_only_answers_confident_taps.py, plus test_a_run_can_choose_the_system_one_judge.py, test_jobs.py, test_jev_reaches_openrouter_without_a_typesafe_key.py, test_guessable_cli_surface.py and test_a_failed_action_still_shows_its_screen.py — none of which this change touches, so they look environment-bound (model routing / keys). Each of those files passes when run alone.

test_web_platform.py::test_web_action_until_executes_once_and_returns_arrival[mcp-input] failed once on the branch while the other suite was running in parallel; alone it passes 7/7 across branch and main, so it's timing-sensitive rather than related.

…t the new paths

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@matibzurovski

Copy link
Copy Markdown
Contributor Author

CI here fails at uv sync --frozen before anything runs — the same failure every push to main has had since 2026-09-20 (the typesafe extra was added without a relock and cannot resolve next to proxy). Fix in #15; this PR goes green once that lands, or I can rebase it onto that branch if you'd rather see it green first.

@eiliya-luzia
eiliya-luzia merged commit 5296f65 into main Sep 25, 2026
0 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants