agent cursor visibility: the rules in Rust (cmux-focus-core) and the app FFI op (B9 step A)
Step A of a two-step FFI move (rust-migration backlog B9). This PR replaces #31781. The agent cursor's visibility rules now exist in Rust, in cmux-focus-core, and the app FFI's JSON channel answers them as the agent_cursor_visibility op. The rules decide where the cursor for a target browser tab draws: on its page, at an anchor in its window, or nowhere.
There are no Swift changes and no behavior change: the app keeps its Swift resolver until step B. Step B is the Swift thin call, its vector suite on a Mac and the deletion of the Swift rules. It is on rust-migration/cursor-visibility-step-b and lands after the bot's CCmuxAppFFI re-pin is on feat-cmux-next.
Check the app FFI pin: expected, step A of a two-step FFI move.
What moves
cmux-tui/crates/cmux-focus-core/src/agent_cursor_visibility.rs ports the rules of Packages/macOS/CmuxNext/Sources/CmuxNextAgentCursorVisibility/AgentCursorVisibilityResolver.swift as they are:
- The window: the front-most window that shows the target's workspace, else one that lists it. Minimized or on another Space: nothing draws.
- Another workspace of that window: its sidebar row, else the window edge.
- The pane off the active screen: the window edge. A column or page scrolled out: the strip edge it left through.
- A background tab, or a selected page that isn't laid out yet: its chip, else the strip.
- A shown page: its viewport, its clip, its zoom and its scale (no scale is 1).
Rects follow CGRect rules: sizes are standardized, an intersection is null when the rects don't meet, and "shows" means more than half a point of overlap on both axes.
JSON has no non-finite numbers, and a CGRect.null that the app converts has infinite x and y. So non-finite numbers travel as "inf", "-inf" and "nan" in both directions. Step B's Swift encoder and decoder use the same strings, so such a snapshot keeps the result the Swift rules gave it.
The op is in cmux-layout-reducer-ffi/src/lib.rs (cmux-app-ffi already links that crate). The request is {"target", "snapshot"}, where the snapshot is AgentCursorVisibilitySnapshot's Codable shape. The answer is the vectors' expect shape plus scale. A malformed request returns ERR_INVALID.
Why the JSON channel and not a typed C ABI: the app resolves once per agent input event, and again on layout or window changes only while a target is tracked. The snapshot holds at most one pane, one chip and one sidebar row per window. Step B runs the perf A/B.
Also: the cmux-tui tree key now lists schemas/agent-cursor-visibility/vectors.json, which the new tests embed (tree-key-embeds.test.sh fails without it).
Changelog
none
Observed / Not run
Observed. Head 0355b6f0bac (feat-cmux-next 9e1c48fa226 merged in). This branch is #31781's Rust commits, re-applied with no Swift history. Every proof commit has a cmux-tui tree identical to the commit the step ran at, checked with git rev-parse <sha>:cmux-tui.
- Red at 9ab32359bd6 (test plus a stub resolver),
cmux-tui-rust-check.sh test agent_cursor_visibility: stepe8bcaea8fadd17bff9d01e80failed. 3 ran, 2 failed: "25 of 26 vectors differ", and the scale case. - Green at ebe44ba1049: step
ff2f9d6976777a31aba6dd0epassed, 3 of 3. - Non-finite red at 390575f10ee (cmux-tui tree identical to ba3d980c646 here): step
f443e12824c30e91ff7c5fe6failed. 4 ran, 3 passed;agent_cursor_visibility_non_finite_numbers_cross_the_wirefailed in the full run and again alone. - Non-finite green at 78cbd40da81 (cmux-tui tree identical to this head): step
1c9f201aabde9547145ae87cpassed, 4 of 4. - C ABI test in
cmux-layout-reducer-ffi: step30c4eaa5853744cee8d63021passed, 5 of 5 (2 of them in that crate). It ran on the never-merge proof refrust-migration/cursor-visibility-ffiproof, which builds that crate inside the cmux-tui workspace (the crate is its own Cargo workspace, so the fleet script never builds it). The op and its test are unchanged since. - At this head: Rust test step
91d7f28d21a10dbb5c8001b1and clippy step418b1fe10b7fb6d1e91f86f7. PENDING - At d26bda03c3c, CI clippy (changed cmux-tui packages) was red on a lint in this PR:
redundant_clonein the new test (run 41403471881). 0355b6f0bac fixes it. tree-key-embeds.test.shpasses. The pre-push gate passes on the pushed range.
Not run:
- Swift: step A changes no Swift file, so I ran no Swift compile-check at this head.
- Line accounting:
swift-logic-lines.pyneedsplans/cmux-next/swift-logic-classes.tsvat the revision, and this branch's base (0511de2bc5e) predates it. With no Swift file changed, the row can't move. Step B reports both rows.
Differences from the Swift rules that no vector covers (the review flagged them; neither can occur with what the app sends):
- The Rust side standardizes the visible
viewportand theworkspaceRowrect; Swift returned them as given. They differ only for a negative size, and the app'sAgentCursorRect(CGRect)always has a non-negative size. - A NaN inside a rect: Rust
f64::minandmaxskip NaN, while CoreGraphics passes it through. The app builds no NaN rect (AppKit view rects andCGRect.null, which is infinite, not NaN).
Non-green checks, classified:
- Check the app FFI pin: expected (above).
- Base findings, red on unrelated PRs or on files this PR doesn't touch:
acpmux tests (lib, hub_integration),tooling-testsdisplay suites, nightly-guards release-notary, andrust workspaces/plan(scripts/journeys/tui-kit/rustfrom #31748; its author was told).