agent cursor visibility: the rules in Rust (cmux-focus-core) and the app FFI op (B9 step A)

Merged
LA
4d ago
+719-3

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: step e8bcaea8fadd17bff9d01e80 failed. 3 ran, 2 failed: "25 of 26 vectors differ", and the scale case.
  • Green at ebe44ba1049: step ff2f9d6976777a31aba6dd0e passed, 3 of 3.
  • Non-finite red at 390575f10ee (cmux-tui tree identical to ba3d980c646 here): step f443e12824c30e91ff7c5fe6 failed. 4 ran, 3 passed; agent_cursor_visibility_non_finite_numbers_cross_the_wire failed in the full run and again alone.
  • Non-finite green at 78cbd40da81 (cmux-tui tree identical to this head): step 1c9f201aabde9547145ae87c passed, 4 of 4.
  • C ABI test in cmux-layout-reducer-ffi: step 30c4eaa5853744cee8d63021 passed, 5 of 5 (2 of them in that crate). It ran on the never-merge proof ref rust-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 91d7f28d21a10dbb5c8001b1 and clippy step 418b1fe10b7fb6d1e91f86f7. PENDING
  • At d26bda03c3c, CI clippy (changed cmux-tui packages) was red on a lint in this PR: redundant_clone in the new test (run 41403471881). 0355b6f0bac fixes it.
  • tree-key-embeds.test.sh passes. 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.py needs plans/cmux-next/swift-logic-classes.tsv at 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 viewport and the workspaceRow rect; Swift returned them as given. They differ only for a negative size, and the app's AgentCursorRect(CGRect) always has a non-negative size.
  • A NaN inside a rect: Rust f64::min and max skip NaN, while CoreGraphics passes it through. The app builds no NaN rect (AppKit view rects and CGRect.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-tests display suites, nightly-guards release-notary, and rust workspaces / plan (scripts/journeys/tui-kit/rust from #31748; its author was told).