feat(cua-driver-rs): Phase 1 native NSPanel onboarding gate (macOS) - #1565
Conversation
Replaces the terminal-only first-launch permissions banner with a small native `CuaDriver Permissions` window when the daemon is launched from the bundled `/Applications/CuaDriver.app`. Closes the most user-visible parity gap with the Swift driver's onboarding flow (issue #1561 + the follow-up "no window on Rust port" report). What this PR ships (Phase 1 only): * A 460x280 NSPanel (`.titled`, `.closable`, level `.floating`) built with raw `objc2 + msg_send!` patterns matching `cursor/overlay.rs`. No new dependencies; no new objc2-app-kit feature flags. The panel lists the missing TCC grants and offers two buttons: - Open System Settings (default action, opens the matching Privacy panes via the existing `open_system_settings_for` path) - Continue anyway (dismisses; gate still polls for grants) Red-dot close is treated as Continue anyway. * A custom `CuaDriverPermissionsPanelTarget` ObjC class registered lazily at first call and memoised in a `OnceLock` so the second panel invocation in the same process reuses the registration. * `gate::run_if_needed` becomes a router. When `panel::panel_enabled()` returns true (main thread, running inside an `.app` bundle, env-var opt-out not set), it calls `panel::show_modal` instead of printing the terminal banner. Bare-binary launches, headless environments, CI, and explicit `CUA_DRIVER_RS_PERMISSIONS_PANEL=0` keep the existing banner + polling flow. Zero regression on those paths. * The post-panel Settings-open is now driven by the user's button choice — Open Settings opens the panes; Continue anyway suppresses the auto-open. Wait-for-grants polling runs in both cases since the user can grant in their own time without the panel's help. Threading: the Serve arm in `cua-driver/src/main.rs` already calls the gate synchronously on the main thread before any worker threads spawn or NSApp.run() is called by the cursor overlay (which only runs in the fall-through MCP arm). `panel::show_modal` asserts `MainThreadMarker` and uses `[NSApp runModalForWindow:]` so the gate blocks the main thread cleanly and returns control before the daemon socket binds. What this PR does NOT ship (deliberately deferred to Phase 2+): * Live red/green status icons that update as grants flip. * Dynamic heading ("needs your permission" → "One more permission" → "ready"). * Auto-dismiss when both grants flip green. * Smart Settings-pane chaining (open second pane when first grant flips). * Hover effects, gear affordances, and "All set" pill. Tested live on this Mac: installed the Phase 1 binary into `/Applications/CuaDriver.app`, ran `tccutil reset` for both grants, launched via `open -n -g -a /Applications/CuaDriver.app --args serve`. Panel rendered as expected (System Events confirmed a `CuaDriver Permissions` window owned by the daemon); both buttons fired their correct outcomes; the daemon proceeded past the gate after the user acted on the panel. 11 of 11 permissions tests passing (`cargo test -p platform-macos --lib permissions::`); env-var parsing tests share a process-global mutex with the existing gate tests to avoid `set_var`/`remove_var` races under parallel `cargo test` runs. Docs updated: `docs/content/docs/cua-driver/guide/getting-started/ installation.mdx` Callout documents the new panel UI, the `CUA_DRIVER_RS_PERMISSIONS_PANEL` opt-out env var, and the bare-binary / headless fallback behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR adds a native macOS AppKit permissions panel for driver startup, with public types and gating, full modal UI implementation, integration into the existing permissions gate, and documentation of the interactive flow and fallback behavior. ChangesPermissions Panel Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
Summary
Replaces the terminal-only first-launch permissions banner with a small native
CuaDriver Permissionswindow when the daemon is launched from the bundled/Applications/CuaDriver.app. Closes the most user-visible parity gap with the Swift driver's onboarding flow.Phase 1 of a planned three-phase rollout (see
planning agent's report— Phase 2 = live status rows + dynamic heading + auto-dismiss; Phase 3 = chaining + always-present + ready strip).What Phase 1 ships
crates/platform-macos/src/permissions/panel.rs(~410 LOC, new) —show_modal(opts) -> PanelOutcomethat builds an NSPanel (.titled+.closable, level.floating, 460×280) listing the missing TCC grants with two buttons:open_system_settings_forpath.Raw `objc2 + msg_send!` patterns matching `cursor/overlay.rs`. No new dependencies, no new objc2-app-kit feature flags. Custom `CuaDriverPermissionsPanelTarget` ObjC class registered lazily and memoised in a `OnceLock` so the second invocation in the same process reuses the registration.
`gate.rs`: `run_if_needed` is now a router. When `panel::panel_enabled()` returns true (main thread + running inside an `.app` bundle + env-var opt-out not set) it calls `panel::show_modal` instead of printing the terminal banner. Post-panel Settings-open is driven by the user's button choice — Open Settings opens the panes; Continue anyway suppresses the auto-open. Wait-for-grants polling runs in both cases.
`mod.rs`: adds `#[cfg(target_os = "macos")] pub mod panel;` and updates the doc-comment to describe the dual presentation surfaces.
What Phase 1 does NOT ship (deferred to Phase 2+)
Threading
The Serve arm in `cua-driver/src/main.rs` already calls the gate synchronously on the main thread before any worker threads spawn — and before `NSApp.run()` is called by the cursor overlay (which only runs in the fall-through MCP arm, after Serve has returned). So `panel::show_modal` can claim the main thread, run `[NSApp runModalForWindow:]`, and return without colliding with the overlay's AppKit loop. `MainThreadMarker::new().expect(...)` panics loudly if anyone wires this up wrong in the future.
Fallback paths (zero regression on these)
The terminal banner + auto-Settings-open path remains in place and is selected when ANY of:
CI / headless runners pick the terminal path automatically without any flag.
Live test on this Mac
Result: panel rendered (System Events confirmed `CuaDriver Permissions` window owned by the daemon), both buttons fired their correct outcomes, daemon proceeded past the gate after the user acted on the panel.
Tests
Docs
Updated `docs/content/docs/cua-driver/guide/getting-started/installation.mdx` Callout to document the new panel UI, the `CUA_DRIVER_RS_PERMISSIONS_PANEL` opt-out env var, and the bare-binary / headless fallback behaviour.
Test plan
Related
tccutil reset Accessibility com.trycua.driver(macOS) #1561 (the umbrella permissions-state issue) — the SR side is already fixed; this PR addresses the user-reported "no window on Rust port".🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation