release: v0.4.11 — stop hijacking the foreground, fix real-device terminate - #190
Merged
Conversation
…minate kuri could not be run on a machine someone was using. Every Simulator input command raised Simulator.app first, because CGEventPost(kCGHIDEventTap) injects into the global event stream and lands wherever focus happens to be. `type` was worse: it shelled out to AppleScript `keystroke`, which goes to the frontmost app, so without stealing focus it would have typed into the user's own window. And `wait-for-ui` polls every 250ms, re-stealing the foreground each time. Input now goes to Simulator.app by pid via CGEventPostToPid, `type` uses Unicode CGEvents, the observation commands do not activate at all, and `open-sim` opens in the background. `--activate` restores the old behaviour per command. Two real-device bugs, both found by writing the tests: - `terminate --device` built `devicectl device process terminate --device <udid> <bundle-id>`, but devicectl's terminate takes `--pid` and no bundle id, so it could only ever die on devicectl's argument parser. `launch --device` now surfaces the pid; `terminate` takes `--pid` or resolves a bundle id itself. - `list-apps --device` silently hid every system app: devicectl's app listing defaults to developer apps only and says nothing about it. Also: `uitree` reported "Simulator.app is not running" when it was running but had no window, which sent you to restart an app that was already up. Verified on hardware — 24 passed / 0 failed against an iPhone 16 Pro Max, 54 passed / 0 failed against a booted simulator, 42 unit tests. The e2e suite now runs in CI on macOS, which is what would have caught the 0.4.6-to-0.4.10 silent `--device` no-op in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gzdKWXk7UbHm5TtSGqYBJ
Tool-surface diff by category, what each covers that the other does not, and how the two test strategies differ. The two findings worth surfacing: physical-device UI automation is missing from *both* projects (theirs runs through AXe against the simulator), so kuri's simulator-only tap/uitree is not a gap against them; and their device "e2e" tests are mocked against captured argv strings on ubuntu-latest, which cannot catch either bug fixed in 0.4.11 — a filtered app listing has correct argv, and a locked device has no argv signature at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012gzdKWXk7UbHm5TtSGqYBJ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why this release exists
kuri could not be run on a machine someone was using. Driving the Simulator seized the foreground window and moved the cursor — on every tap, and every 250ms during a
wait-for-ui. That is fixed, along with two real-device bugs that the new tests surfaced.kuri no longer takes over your machine
Every input command called
sim_window.activatefirst. It had to:CGEventPost(kCGHIDEventTap, …)injects into the global event stream, so events land wherever focus happens to be, and Simulator.app had to be raised for a tap to hit the right thing.tapdoubletaplongpressswipegesturetouchkeykey-sequencebatchCGEventPostToPid→ straight into Simulator's queuetypekeystroke→ goes to the frontmost appCGEvents addressed to the piduitreefindwait-for-uiactivate()buttonbackgroundactivate()AXPress, which never needed focusopen-simopen -a(raises it)open -g(background)typewas the worst of them: because AppleScriptkeystroketargets whatever is frontmost, it had to steal focus to be correct — and if it ever ran without doing so, it would have typed your text into whatever you had open.wait-for-uiwas a close second, re-stealing the foreground on every poll for the length of the wait.--activaterestores the old behaviour per command, off by default.Real-device fixes
terminate --devicecould never have succeeded. It built:but devicectl's terminate takes
--pidand accepts no bundle id at all, so every invocation died on devicectl's own argument parser. Now:The bundle-id form resolves a running pid by matching
device info processesagainst the app's on-device bundle URL. A launch that reports success without an identifier is now an error rather than a silent zero — which would otherwise terminate an unrelated process later.list-apps --devicesilently hid every system app.devicectl device info appsdefaults to developer apps only and nothing in the output says so, so a command documented as "list installed apps" returned a handful of entries on a phone with hundreds, and exited 0. The same defaulting broke bundle-id lookups, so terminate-by-bundle-id could not resolve a system app either.list-appson the simulator no longer demands--udid— it resolves the booted simulator like every other simulator command already did.Diagnosis
"Simulator.app is not running" when it was running. The accessibility tree hangs off a window, and
simctl bootdoes not open one — so a running-but-windowless Simulator produced an error that sent you to restart an app that was already up. Now a distinctSimulatorHasNoWindowerror carrying the real remedy, anddoctorreports window presence rather than just the process.Tests
zig build e2e-ios-device— new suite against attached hardware. Inspection, the install → list-apps → launch → terminate → uninstall round trip both by pid and by bundle id, and assertions that the XCUITest-only commands still refuse cleanly while a real device is attached. Skips with a reason when nothing is plugged in, no bundle id is set, or the screen is locked — phones re-lock on their own timeout and SpringBoard refuses every launch while they are, which is the environment, not a defect.e2e-ios, needing no hardware. They pin the silent-success class fixed in 0.4.10. Two assert the absence of devicectl's argument-parser complaint — that is what separates "the device is missing" from "we called devicectl wrong", i.e. exactly the terminate bug above.e2e-iosdegrades instead of failing on preconditions a machine cannot supply (Accessibility grant, Simulator window, Xcode toolchain), which is what lets it be a CI gate rather than a permanent red build on a runner that can never hold a TCC grant.list-apps,status-bar,ui appearanceset-and-read-back,set-location/reset-location,log --last,terminate.0, and/var/Demo.appendixmust not match as being inside/var/Demo.app.Verified
e2e-ios-devicee2e-iose2e-ios(headless)zig build testNot verified: the pid-targeted input path.
CGEventPostToPidand the Unicodetypecompile and are the correct APIs for background delivery, but no tap/swipe/type has been run against a simulator since the change — the e2e suites deliberately never post input events, since doing so seizes the cursor of whoever is running them.CI
The e2e suite finally runs on every push. A new
mobile-macosjob builds kuri-mobile, runs its unit tests, boots a simulator and runs both suites. Nothing previously caught a regression on the device path — which is how the silent--deviceno-op survived from 0.4.6 to 0.4.10.It picks and boots the simulator through kuri-mobile itself rather than
xcrun simctl: partly to exerciselist-devicesandbootfor real, and partly because barexcrunresolves throughxcode-select, the exact indirection whose failure mode this project exists to avoid. The job never opens Simulator.app, so it runs headless and the accessibility cases skip.kuri-mobile's unit tests now also run on the Linux job — it has its own
build.zig, so the rootteststep never reached them.🤖 Generated with Claude Code
https://claude.ai/code/session_012gzdKWXk7UbHm5TtSGqYBJ