Skip to content

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

Merged
lawrencecchen merged 9 commits into
feat-cmux-nextfrom
rust-migration/cursor-visibility-step-a
Nov 2, 2026
Merged

lawrencecchen merged 9 commits into
feat-cmux-nextfrom
rust-migration/cursor-visibility-step-a

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

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 0355b6f (feat-cmux-next 9e1c48f 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 9ab3235 (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 ebe44ba: step ff2f9d6976777a31aba6dd0e passed, 3 of 3.
  • Non-finite red at 390575f (cmux-tui tree identical to ba3d980 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 78cbd40 (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 d26bda0, CI clippy (changed cmux-tui packages) was red on a lint in this PR: redundant_clone in the new test (run 41403471881). 0355b6f 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 (0511de2) 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:

…Rust rules (failing)

The Rust home of the agent cursor visibility rules (backlog B9) gets its
types and the replay of schemas/agent-cursor-visibility/vectors.json, ported
from the deleted Swift AgentCursorVisibilityVectorTests, plus the page scale
case. The resolver is a stub that answers notDrawn(tabClosed): 25 of 26
vectors fail until the rules move in the next commit.
The rules of CmuxNextAgentCursorVisibility's AgentCursorVisibilityResolver,
moved as they are: the window that shows (else lists) the target's
workspace, minimized and other-Space windows draw nothing, another
workspace anchors at its sidebar row (else the window edge), a pane off
the active screen at the window edge, a scrolled-out column or page at the
strip edge it left through, a background tab at its chip (else the strip),
and a shown page draws in its viewport with its clip, zoom and scale.
CGRect semantics: sizes are standardized, intersection is none when the
rects do not meet. No behavior change; the Swift copy goes in PR 2.
…SON channel

cmux_layout_reducer_json answers agent_cursor_visibility ({target,
snapshot} in, the visibility in the vectors' expect shape out) from
cmux-focus-core's rules. cmux-app-ffi links this crate already, so the next
CCmuxAppFFI release carries the op; the Swift resolver calls it in PR 2.
The C ABI test sends every vector through the op.
…s-core tests embed

Also lists schemas/settings/source/agent-retry.json, which acpmux embeds
and the key missed on the base (tree-key-embeds.test.sh red at 0511de2).

(cherry picked from commit 51e3894)
…y wire as strings (failing)

The app's JSONEncoder refuses non-finite numbers, and a CGRect.null the
snapshot builder converts has infinite x and y. The Swift rules gave a
result for such a snapshot; the Rust rules must give the same one.

(cherry picked from commit 390575f)
…y wire as "inf", "-inf", "nan"

Every number of the snapshot and of the answer reads and writes the
strings the Swift side's JSONEncoder/JSONDecoder convertToString and
convertFromString strategies use, so a snapshot with a converted CGRect.null
or an infinite scale gets the result the Swift rules gave it.

(cherry picked from commit f21ff3a)
@lawrencecchen lawrencecchen added the risk:low Risk tier (docs/ci/risk-tiers.md) label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 0355b6f: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr31934 -f ref=rust-migration/cursor-visibility-step-a -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 9e1c48f to compare; do not merge until green; see lane-rules

Shipped in:

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/41407469077 at 7b24abd failed: cmux-next checks (god files, concurrency, crash safety, l10n).
Those jobs last passed at 6660f71. This pull request is one of 5 merged since: #31842, #31934, #31925, #31968, #27364.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

… warnings)

clippy (changed cmux-tui packages), run 41403471881 job 125140181043,
failed on redundant_clone at agent_cursor_visibility.rs:158.
@cursor

cursor Bot commented Nov 2, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (claude-review-subagent): SHIP at 0355b6f

Checked (independent review, correctness first):

  • Rule equivalence with AgentCursorVisibilityResolver.swift, line by line: window choice, minimized / other Space, workspace row vs window edge, missing pane, frame or clip, column edge sides and the 0.5 thresholds, span fallback, chip / strip fallbacks, visible viewport, clip, zoom and scale. CGRect semantics: standardization, a null intersection when the rects do not meet, touching rects (zero size, not shown). With x or y = +inf (a converted CGRect.null), the result is "no intersection", the same as Swift's isNull. The three null-derived results the tests assert match CoreGraphics.
  • Non-finite codec: covers every f64 (Rect x/y/w/h, page zoom and scale, the answer's zoom and scale). Integer JSON numbers decode through the untagged Wire. Optional scale: a missing key or null gives none. No path writes a non-finite number as JSON null. No crate in the focus-core or ffi dependency set enables arbitrary_precision.
  • Wire shape against Swift's synthesized Codable: names, camelCase, optionals, [String: AgentCursorRect] maps. The answer is kind-tagged.
  • Tests: red at 9ab3235 (stub resolver) and at 390575f / ba3d980 (a plain f64 cannot read "inf"). No test passes vacuously. The C ABI test sends every vector and checks that a malformed request is refused.
  • Compile and style: clippy (changed cmux-tui packages) is green at this head. The fix 0355b6f changes only the redundant clone at tests line 158. rustfmt --check (edition 2024, max_width 100, Max heuristics) passes on all changed Rust files.
  • Layering: rules move to Rust (layer-ownership.md model 1, L1/L4). Step A holds no Swift change and no behavior change.
  • Tree inputs: tree-key-embeds.test.sh passes on an exported head (205 embeds). When the new line is removed, the check fails and names it.

Non-blocking notes:

  • Rust standardizes the visible viewport and the workspace-row rect. Swift returned these raw. They are equal under CGRect ==; they differ only for negative sizes, which no vector has. NaN inside a rect can differ, because f64::min/max ignore NaN. The PR body now lists both.
  • serde_json without float_roundtrip may parse a float 1 ULP off from Swift's exact parse. This has no practical effect on the 0.5 thresholds.
  • Consider adding an agent cursor visibility row to layer-ownership.md section 2 with step B.

@lawrencecchen lawrencecchen added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Nov 2, 2026
@lawrencecchen
lawrencecchen merged commit 69ddef1 into feat-cmux-next Nov 2, 2026
187 of 195 checks passed
@lawrencecchen
lawrencecchen deleted the rust-migration/cursor-visibility-step-a branch November 2, 2026 07:30

This branch was previously deployed

3 inactive deployments
artifacts — 0355b6f0 Deployed Nov 2, 2026 by lawrencecchen via publish tree-addressed cmux-tui #99947
sentry-dsyms — 0355b6f0 Deployed Nov 2, 2026 by lawrencecchen via upload cmux-tui dSYMs to Sentry #99947
ci-routing-agent — 0355b6f0 Deployed Nov 2, 2026 by lawrencecchen via cmux-tui tree preflight #99947
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant