# rightkit-control
Shared native desktop control APIs for Right Suite applications. Provides JSON helpers, keyboard input, WebDriver bridging, and platform-specific accessibility or embedded control modules.
### Hidden macOS pointer input
Embedded Tauri targets receive native `NSEvent` down/drag/up calls on the
WKWebView responder, on AppKit's main thread. This bypasses window first-mouse
handling without making a window key, showing it, or activating its app.
WebKit reads `+[NSEvent pressedMouseButtons]` to build Pointer Events, so the
embedded path supplies a thread-local button mask during dispatch. A second
scoped hook bypasses WKWebView's asynchronous pointer text-input interception;
both Objective-C hooks forward to their original implementations outside that
dispatch. No DOM input events or global HID events are synthesized.
`pointer {phase:"down"|"drag"|"up"|"move",x,y,button?,modifiers?}` supports
scrubs split across requests. `click`, `move`, and composite `drag` share native
delivery; x/y are viewport CSS pixels. Pointer calls retain effect admission,
allowlists, and optional input-lease fencing. `rightkit-qa::Control::pointer`
uses this service for hidden launches. Non-embedded callers keep the per-pid
CGEvent fallback, which cannot guarantee first-mouse delivery to inactive views.
`examples/control-test-app/proof.sh` proves trusted Pointer Events, capture,
scrub value changes, modifier clicks, hidden/non-key readback, and foreground
stability throughout the run. `TARGET` names the workspace managed debug
directory; `APP_TARGET` names the standalone fixture's managed debug directory.
The `tauri-plugin` feature enables the embedded Tauri control server.
### Native viewport sizing
`set_viewport {width:1440,height:1000}` accepts integer CSS dimensions in
`1..=16384` & returns measured `innerWidth`/`innerHeight`. It passes command
effect admission & never activates, orders, or makes windows key. macOS computes
`frameRectForContentRect:` on AppKit's main thread & temporarily subclasses only
target NSWindow to bypass `constrainFrameRect:toScreen:` during `setFrame:display:NO`.
Original class is restored immediately. Windows uses `SetWindowPos` with no move,
z-order change, or activation. Other platforms return typed `unsupported`.
macOS explicitly resizes hidden WKWebView & completes native layout before
polling page dimensions outside AppKit's main-thread block. Readback retries
zero/stale dimensions & transient JS failures for up to 2 s, including callback
timeouts; failure reports requested & last measured dimensions.
`rightkit-qa::Control::set_viewport(width, height)` returns measured dimensions.
WKWebView snapshots explicitly cover full bounds; fixture proof checks hidden
1440×1000 CSS content, scale-aware PNG dimensions & foreground stability.
Publish through the RightKit release workflow.
## Control-plane guarantees (0.1.2)
- **EFF-001 admission** (`admission::EffectGate`): every effectful request is
validated, approved by a host `AdmissionHook` (trait or closure), executed,
and settled as `ok | denied | failed | cancelled`. A denial (or a panicking
hook) happens before any side effect. `webdriver::Server::with_gate`
(click, send keys, screenshot), the `tauri-plugin` `Control::gate`,
`mac::Gated` and the `rightkit-control` CLI route every effectful entry
point through it; the default gate is allow-all, so existing behaviour is
unchanged.
- **PTY-002 input lease** (`lease::InputLease`, `lease::ControlSession`):
acquire bumps the epoch and fences the previous holder; input carries
`(epoch, sequence)`; stale epochs are rejected, acknowledged duplicates are
deduplicated without re-execution, gaps are rejected, and ambiguous
(unacknowledged) input must be explicitly reconciled before new input.
`Control::input_lease` makes the plugin's input methods require
`leaseEpoch` + `inputSequence` and adds `lease_*` methods.
- **PTY-002 durable state** (`lease::InputLease::open`, `lease_store`):
optional `LeaseStore` backing (in-memory stays the default) with an atomic
`FileLeaseStore` (temp file, fsync, rename, directory fsync, checksum).
Every change is saved before it takes effect: an epoch is granted only once
durable, and an input sequence is reserved durably before its executor
runs. Each lease has a durable `ExecutionIdentity` and an
`ExecutionState` (`Running | Exited | Unknown`); input is accepted only
while `Running`. On restart the epoch is fenced forward (never backwards),
the lease is detached, `Running` comes back `Unknown`, and input that was
unacknowledged at the crash comes back as `Unknown` input
(`unknown_input`, `input_status`) that must be resolved explicitly with
`acknowledge_input(epoch, seq)`, `reconcile` or `acquire_reconciling`.
A corrupt or foreign record fails closed rather than resetting.
- **Lifecycle events** (`events::ControlEvent`): content-free
start/stop/crash/denied and lease acquired/fenced/released/rejected events
via an `EventSink` callback.
## CLI admission
`rightkit-control [--policy FILE] [--events] <command> ...` admits every
effectful command (`launch click hover drag scroll click-text press keywin
keyraw type key shot`, and `serve`'s click / send keys / screenshot) through
one gate. Without a policy the gate is allow-all (output unchanged); with
`--policy FILE` or `RIGHTKIT_CONTROL_POLICY` it applies `cli::Policy`
(`default allow|deny`, then first-match `allow|deny <kind|*> [method|*]`
lines). An unreadable or invalid policy exits 2; a denial exits 3 and does
nothing. `--events` / `RIGHTKIT_CONTROL_EVENTS=stderr` prints content-free
lifecycle events to stderr. Read-only commands are not effects.
## Known gaps
Closed in 0.1.2:
- PTY-002 durable lease state and execution identity/state across host
restart or crash (previously in-memory only).
- Ungated paths: the effectful `mac` primitives are now crate-private behind
`mac::Gated`, reachable ungated only through the explicitly named
`mac::unsafe_ungated` opt-in; the CLI admits through a gate with an optional
policy; the WebDriver screenshot is now admitted as a `capture` effect.
Remaining:
- **Breaking for direct `mac` callers:** `mac::click`, `type_text`, `key`,
`ax_press`, `click_node`, `launch_hidden`, `capture_window`, `ax_make_key`,
`key_without_raise`, `hover`, `drag`, `scroll` moved to `mac::Gated` (or
`mac::unsafe_ungated`). Reads (`windows`, `main_window`, `frontmost_*`,
`ax_dump`, `ax_find`, `ax_text`, `accessibility_trusted`) are unchanged.
- `webdriver::macos::AxBackend` is an adapter whose `Backend` methods act
directly, and `Server::backend` is a public field; only calls through
`Server::handle`/`serve` are admitted.
- `FileLeaseStore` assumes one owning process per file (no cross-process
lock). If the post-execute acknowledgement save fails, the durable record
stays conservative (that input recovers as `Unknown`) and
`InputLease::store_degraded()` reports it.
- `acquire_reconciling` panics if a durable store refuses the save; durable
callers should use `try_acquire_reconciling`.
- The holder is identified by its epoch only (no holder label is stored).
- Recovery happens in `InputLease::open`, before an event sink is attached,
so no recovery event is emitted; inspect `record()` after opening.
- `LeaseError` gained variants (`ExecutionNotRunning`, `UnsentSequence`,
`NonMonotonicAck`, `InvalidExecutionTransition`, `Store`); exhaustive
external matches need a new arm.
- The `tauri-plugin` lease methods do not yet expose durable state or
`acknowledge_input` over the wire.