Skip to content

Reactive CoreSimulator device state (replace simctl list polling)#121

Draft
EvanBacon wants to merge 3 commits into
mainfrom
@evanbacon/sim-reactive-state
Draft

Reactive CoreSimulator device state (replace simctl list polling)#121
EvanBacon wants to merge 3 commits into
mainfrom
@evanbacon/sim-reactive-state

Conversation

@EvanBacon

Copy link
Copy Markdown
Owner

Why

Device enumeration went through xcrun simctl list devices, spawned per call across the CLI and middleware (~50ms each), and the web grid polled /grid/api on a fixed 750ms–3s interval. simctl is a thin CLI over CoreSimulator.framework, which already holds a live, XPC-subscribed view of the device set from the com.apple.CoreSimulator.CoreSimulatorService daemon. We can read and subscribe to that directly, in process, and push changes to the UI instead of polling.

How

  • Added SimDeviceMonitor (Sources/SimNative), a process-global subscriber that drives CoreSimulator through the Objective-C runtime (dlopen + @objc protocol shims, no linking). It holds the default device set, registers set-level handlers (add/remove) plus per-device handlers (state changes), and keeps a live snapshot. Exposed over N-API as a synchronous listDevices() and a SimWatch push class.
  • TS device.ts now serves the simctl list devices -j shape from that reactive snapshot via listDevicesByRuntime / tryListDevicesByRuntime, with a one-shot simctl fallback when the native addon is unavailable. The null-returning variant preserves the lookup-failed vs empty distinction so stale-helper pruning stays safe. findBootedDevice, resolveDevice, and the per-request reads in index.ts / middleware.ts route through it; the old time-based booted-set caches are gone.
  • Live device grid: a new /grid/api/events SSE endpoint pushes the same payload as GET /grid/api whenever the device set changes (reactive subscriber) or a helper starts/stops (state-file watch). The client useGridDevices hook subscribes over the existing exec websocket instead of polling. Changes made outside serve-sim (Simulator.app, simctl, Xcode) now reflect in the UI immediately.
  • Factored the duplicated SSE scaffold shared by /api/events and /grid/api/events into one streamDeviceStateSse helper.

Test Plan

  • tsc --noEmit and oxlint clean; native addon builds (build.ts).
  • listDevices() output matches simctl list devices -j exactly (52/52 devices); first call ~600ms (subscribe), cached reads ~0.3ms.
  • Ran the built CLI server and opened the /grid/api/events stream while driving a device through simctl: observed live pushes Shutdown -> Booting -> Booted -> Shutting Down -> Shutdown with no polling, before and after the SSE refactor.

Introduces a new native CoreSimulator device monitor and exposes `listDevices`/`SimWatch` through the addon, then wires TypeScript to use that live snapshot instead of repeatedly spawning `xcrun simctl list`. Device resolution, booted checks, and simulator listing now share this reactive source with `simctl` fallback when native access fails. The client grid hook is switched from interval polling to server-pushed SSE updates, and middleware now serves `/grid/api/events` plus native device-change subscriptions so external boot/shutdown changes propagate immediately.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a16bab0-b9a9-4428-bb6a-8fc33c70bd8a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch @evanbacon/sim-reactive-state

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Move booted-UDID lookup logic into `device.ts` as `listBootedUdids()` and export `eachDevice()` for shared iteration, then update CLI and middleware to use the centralized helpers. This removes duplicate fallback logic around device-set reads, keeps the null-on-read-failure behavior for destructive paths, and slightly simplifies simulator listing by spreading existing device fields.
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.

1 participant