release: v0.5.5#55
Merged
Merged
Conversation
…tex token) jsonwebtoken 10 ships no crypto backend by default (default = ["use_pem"]), so the 9->10 bump compiled fine but RS256 signing panic!'d at runtime; with panic = "abort" in release that aborted the whole app the moment Billy requested a Vertex access token. Debug builds swallowed the panic via tokio unwind, which is why it only crashed in production. Adds a regression test that signs with a throwaway RSA key so the actual signer path runs (existing tests only covered pre-signing error paths).
The native simulator preview pushed raw RGBA frames at 60 Hz with a 900px cap (~1.5 MB/frame, up to ~90 MB/s) through the Tauri IPC channel, saturating the webview main thread and making typing in the YAML editor lag. - SIM_FPS 60 -> 30, SIM_MAX_DIM 900 -> 700 (~75% less IPC traffic) - drop the redundant 1.5 MB per-frame copy on the JS side (ImageData accepts an offset view over a plain ArrayBuffer) - align the sim_capture smoke test with the real preview params
- Extract the fps badge into its own component so the periodic fps updates from streamStore re-render only the badge, not the whole Toolbar subtree - Move View-menu panel entries into per-item components subscribing to their own visibility flag, removing Toolbar's subscription to the visible record - Memoize ChatMessage: during streaming only the appended-to message changes identity, so other messages now skip their ReactMarkdown re-render
After a crash or SIGKILL, kill_on_drop never fires and the studio JVM (holding the XCTest driver on :22087) outlives the app. Stale instances then fight the fresh one for the driver and the inspector hangs on a zombie runner for the whole readiness budget (observed: 4 orphan studios for the same UDID, inspector unusably slow). Mirror the Android path's kill_orphan_studios, scoped to the target UDID so a legitimate Android studio keeper is never touched.
… guards Root cause of the production-only SIGABRT when interacting with a booted simulator (symbolized: attach() displayClass msg_send, sim_capture/mod.rs): CoreSimulator's ROCK proxies throw ObjC exceptions when the XCTest runner churns the device IO state. sim_capture wraps those sends in objc2::exception::catch, which needs the exception to UNWIND through Rust frames to reach its @catch — but panic = "abort" compiles every Rust frame as non-unwindable, so the exception aborted the whole app before the catch fired ("panic in a function that cannot unwind"). Debug builds unwind, which is why this never reproduced outside production. Also adds a matrix of manual repro tests (screen churn, restart cycles, XCTest touch injection, concurrent simctl screenshots, concurrent double-attach) and dedupes the booted-udid helper.
…atch_unwind The real crash, finally symbolized and reproduced in a test: NSInvalidArgumentException: -[ROCKImmutableProxy displayClass]: unrecognized selector sent to instance Once the XCTest runner (maestro studio) touches the simulator's device IO, the descriptor proxies become ROCKImmutableProxy instances that throw on some selectors. The raw ObjC exception unwound into Rust and aborted the process before objc2::exception::catch could fire — dropping panic=abort was necessary but not sufficient. - objc2 feature: every msg_send is wrapped in @try/@catch at the C level, converting thrown exceptions into catchable Rust panics - sim_capture: fragile proxy sends now use a (catch_unwind) helper, so displayClass/framebufferSurface throws fall back gracefully (capture keeps working via the ioSurface path — verified by the capture_with_touch_injection repro test) - poll thread gets a catch_unwind backstop: worst case the preview stops, the app never dies
…per) The bridge JVM (maestro studio) can outlive the XCTest runner it launched: the process stays alive while nothing listens on :22087 anymore. The keeper only checked is_process_alive, so it was reused forever and every tap / Home press failed with 'driver unreachable' until a manual reconnect. ensure_ios_keeper now also requires is_healthy(): once a keeper has been ready, a /status probe (cached 5s) must still answer; otherwise the keeper is recycled — the respawn path also culls the zombie studio via the orphan killer, and the runner gets reinstalled/relaunched.
…assthrough The bridge-install button (and any other download, incl. Vertex token exchange) failed with an IPC error on a corporate machine while GitHub worked fine in the browser. Two gaps: - reqwest was built with plain rustls-tls (bundled Mozilla roots only), so a TLS-inspection proxy's MITM CA — trusted by the OS keychain, hence the browser — was rejected by the app. Switch to rustls-tls-native-roots. - Finder-launched apps don't inherit shell proxy vars; enrich HTTP(S)_PROXY / ALL_PROXY / NO_PROXY from the login shell like PATH.
Plugging in (and connecting) a physical iPhone made the app unusable: PHYSICAL_SCREENSHOT_INTERVAL_MS was 0, so /screenshot PNGs were polled back-to-back, and each multi-MB frame crossed the Tauri event bridge as a JSON number array — 4-15 MB of JSON parsed token-by-token on the webview main thread, continuously. - base64-encode PNG frames in ios_frame/web_frame payloads (one string token, ~4x smaller, orders of magnitude faster to parse); decode at the ipc.ts boundary - physical poll interval 0 -> 150 ms (~3 fps real-world either way, but the main thread gets breathing room between frames)
…ed capture The fps ceiling was the on-device capture itself: XCUIScreen.screenshot + pngRepresentation is ~200-300 ms per frame, serialized one request at a time. Two levers, both server-side supported already: - /screenshot?compressed=true: the runner's jpegData(0.5) encode is much faster than PNG and the payload ~10x smaller (frontend now types the Blob by sniffing the JPEG magic) - pipeline the polling across 2 concurrent lanes so capture overlaps transfer; a shared ticket + newest-emitted watermark drops frames that complete out of order so the preview never steps backwards ~3 fps -> roughly 8-12 fps expected; per-lane 50 ms pause keeps the webview main thread responsive (frames are small now).
…sibility Probe for the future high-fps physical preview: enables CoreMediaIO's AllowScreenCaptureDevices opt-in, finds the plugged iPhone's muxed DAL device via AVFoundation, runs a capture session for 5s and reports fps + frame size. Run with the iPhone plugged in and unlocked: cargo run --manifest-path src-tauri/Cargo.toml --example avf_probe Expects a one-time CAMERA permission prompt for the terminal. If this yields 30-60 fps, it becomes the physical analogue of sim_capture (wired through the existing preview Channel); the /screenshot poll stays as the fallback.
Wire the avf_probe-validated capture path (27.2 fps @ native pixels on an iPhone 17 Pro) into the live preview as the physical analogue of sim_capture: - new avf_capture module: CMIO AllowScreenCaptureDevices opt-in, camera TCC request/check, muxed DAL device matched by uniqueID/UDID (or the only one present), AVCaptureSession + BGRA video-data output whose delegate downscales (nearest-neighbour, longer side <= 700) and try_sends Frames; all !Send AVF objects live on a dedicated thread (sim_capture's threading model), ObjC exceptions contained the same way - preview.rs: CaptureSource enum picks AVF for physical keepers, the existing drain/Channel/frontend pipeline is reused unchanged - src-tauri/Info.plist: NSCameraUsageDescription (without it TCC kills the app on first AVF touch); macOS exposes the device SCREEN as a camera-class device, the phone camera itself is never used - objc2-av-foundation promoted from dev-dependency to macOS dependency Fallback unchanged: camera denied / no DAL device / any error -> the pipelined JPEG /screenshot poller (~8-12 fps) keeps the preview alive.
…driver upgrade_ios_preview gated every native preview behind wait_until_ready, but on a first physical connect the driver BUILD takes ~10 min while the readiness budget is 180 s — the upgrade timed out, fell back to the /screenshot poller (which needs the same driver), and the preview stayed blank for the entire build. The AVF mirror is driver-independent (the frame IS the device screen), so physical keepers now skip the wait entirely: the screen shows within seconds of plugging in, while the driver keeps building in the background for inspect/tap. The simulator path still waits (its crop needs device_info from a ready driver).
…USB mirror The bundle is signed with the hardened runtime (notarization requirement), under which camera-class device access needs the com.apple.security.device.camera entitlement on top of the Info.plist usage description. Without it AVFoundation listed ZERO capture devices and never showed the TCC prompt — the mirror failed with 'no USB screen-capture device appeared' while the unsigned avf_probe worked fine.
setSampleBufferDelegate:queue: does NOT retain the delegate; ours was a
local dropped at the end of setup, deallocating the delegate and its
frame Sender — the channel closed instantly ('preview frame channel
closed' right after attach) and the mirror stayed blank. The capture
thread now holds a LiveCapture { session, delegate } for the whole run.
The iPhone's muxed DAL device can take 10s+ to (re)appear after plugging in or after a previous capture session died; one failed attempt used to strand the preview on the JPEG poller until a manual reconnect. Discovery budget 10s -> 20s, and the frontend retries the upgrade up to 5 times (5s apart) instead of fire-and-forget-once.
Same zombie class as simulator studios: after a crash/SIGKILL the bridge outlives the app AND keeps its port bound. Each leftover instance holds a 600x port, so every fresh bridge binds the NEXT free one (observed: nine orphans squatting 6001-6009) while the keeper's HTTP client polls PHYSICAL_BRIDGE_PORT forever — the mirror streamed fine but taps/inspect were dead even though the live bridge printed Ready (on 6009). Cull any bridge for the target UDID before spawning so the fresh one always gets 6001.
The first inspect right after the bridge rebinds its port commonly hits one transient 'driver unreachable'; the immediate retry succeeds. Retry once internally instead of making the user click Inspect twice.
maestro-ios-device's fatal() uses fmt.Printf, so its failure reason lands on STDOUT — the install command only relayed stderr, yielding the useless 'setup failed: ' with no detail. Relay the tail of both streams.
Bundled locally via @fontsource/manrope (400-800) so the desktop app stays offline-capable; wired as the Tailwind sans stack and into the pre-hydration splash. Mono stack unchanged for the console/editor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A failed silent (startup) update check set phase to "error", which opened the UpdateDialog regardless of the silent flag — so a flaky network or a 404ing update endpoint popped "Update failed" on every launch. Silent checks now just warn and fall back to not-available; only user-initiated checks surface the error.
scrcpy v3.3.1 crashes on Android 16: IDisplayWindowListener gained an onDisplayAnimationsDisabledChanged method the bundled server doesn't implement, so DisplaySizeMonitor throws AbstractMethodError, the server thread aborts after a few frames, and the mirror freezes on the first frame (video stream closed by peer). Upstream scrcpy #6362, fixed in v3.3.3. Bumps the bundled server jar, SCRCPY_VERSION/JAR_FILENAME, and the download script's pin + SHA-256. v3.3.4 stays backward-compatible with Android 15 / One UI 7 and uses no changed server args, so physical Android and emulator both work; iOS doesn't use scrcpy at all.
Web support is beta and unstable, so the synthetic Web Browser (Chromium) target is now hidden from the device list unless the user opts in via a new "Web Browser target (beta)" setting (off by default). The backend still always returns the target; the device list filters it out client- side. The active connection is never hidden, so a user can still disconnect web after toggling the setting off.
…ubgroups General was a grab-bag mixing appearance, editor, updates, and app behavior. Introduce a SettingsSubgroup primitive and regroup: - General → Appearance / Editor / Application - Device & Performance → Mirroring / Inspector / Monitoring / Beta targets Move the Web Browser target toggle out of General into Device's "Beta targets" group (it's a device target, not a general preference) and give it a beta badge matching the experimental one. No behavior change.
The console renders 'logs' in Technical mode but 'steps' in Simple mode (the default). clearLogs only reset 'logs', so in Simple mode the Clear button appeared to do nothing. Replace it with clearConsole, which clears logs, steps, exitCode and stopRequested — a full reset regardless of mode. Clear is now disabled while running (wiping steps mid-run would drop the live step list) and when both views are already empty.
… run On an iOS simulator, the maestro studio driver and `maestro test` both need the XCTest driver on :22087 and can't coexist. run_flow stops the keeper before launching the test, but an inspector auto-dump (or a tap) would call ensure_ios_keeper and re-spawn a competing `maestro studio`, deadlocking both on :22087 so the run never starts. Add an ios_sim_run_active flag (AppState): set on sim-run spawn, cleared on the runner's exit. ensure_ios_keeper refuses to re-warm a simulator keeper while it's set; the inspector also pauses background dumps while a run is in flight. Physical iOS (reuses the bridge) and Android are unaffected.
The first inspect on a cold iOS simulator blocks ~1-2 min on the XCTest driver cold-start. Two improvements: - Bump MAESTRO_DRIVER_STARTUP_TIMEOUT 120s -> 180s to match our own readiness budget. At 120s maestro studio threw IOSDriverTimeoutException while we were still waiting, so a slow-but-fine cold start failed. - Inspector shows 'Starting iOS simulator driver... first inspect can take ~1-2 min' instead of a silent 'Dumping hierarchy...' that reads as a freeze, and the timeout error is now actionable.
…reconnect A plain disconnect now leaves a simulator's `maestro studio` keeper running instead of killing it, so reconnecting the same booted sim reuses the already-warm XCTest driver (seconds, not the ~1-2 min cold start). teardown_all_sessions/teardown_ios take a keep_ios_sim_warm flag: disconnect passes true, quit passes false (no orphans). Physical bridges are never kept warm. A lingering warm keeper is retired by ensure_ios_keeper (different iOS device) or at the top of connect_device (switching to Android/Web), since only one studio can hold :22087.
upgrade_ios_preview retired the screenshot poller the moment the native capture *attached*, assuming attach == frames flowing. On a physical iPhone whose AVF USB capture attaches but never delivers sample buffers (seen on iPhone 17 Pro / iOS 26.5.1), the poller was killed before the driver was even ready, leaving the mirror blank forever and the device unusable. spawn_ios_preview now returns a receiver that fires on the first frame actually pushed to the channel. The poller is retired only then; if the capture stays mute the receiver resolves Err when the task ends and the screenshot poller keeps mirroring via /screenshot once the driver is up. Simulators deliver a framebuffer frame immediately, so their behavior is unchanged. Disconnect still retires the poller via the same state slot.
On a physical iPhone the screenshot poller IS the preview, hitting /screenshot on the single :22087 forward every 50ms. With the previous commit keeping that poller alive for a mute-AVF device, the flood saturated the maestro-ios-device bridge so an inspect's /status and /hierarchy were starved — inspect hung on 'waiting for iOS driver'. Add an ios_inspect_active flag: enter_inspect_mode sets it (reset via a drop guard on every return) and the physical screenshot worker skips polling while it's set, yielding the bridge to the hierarchy dump. The preview briefly freezes during the dump, then resumes. Simulators are unaffected (their poller uses simctl, not the bridge).
Inspector state is global, not per-device, and nothing reset it when the connected device changed — switching devices left a stale tree and could strand 'loading' on a dump that targeted the previous device, so the Inspect control sat in a permanent spinner. - disable() now also clears 'loading' and the dumpInFlight guard (a background dump's .then early-returns once disabled and never cleared loading itself). - App resets the inspector whenever the connected device serial changes (switch / connect / disconnect).
|
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.

Release v0.5.5 — bug-fix release focused on iOS (simulator + physical iPhone), Android 16, inspect mode, and settings.
Highlights (user-facing)
iOS
Android
General
Release mechanics
CHANGELOG_EN.mdupdated..app.tar.gz+.sig) andlatest.jsonare ready to attach to the GitHub release once merged.Once merged, the release is tagged
v0.5.5and published as Latest so the in-app updater endpoint resolves to it.