agent cursor visibility: Swift calls the Rust rules over the app FFI; the Swift rules are deleted (B9 step B)

Merged
LA
5d ago
+290-103

Step B of a two-step FFI move (rust-migration backlog B9). The agent cursor's visibility now comes from the Rust rules that #31934 landed. #31974 re-pinned the app to cmux-app-ffi-c94408d3c4853068b32e73e50afd3cbbeea776c7, which carries them. The Swift copy of the rules is deleted. This PR has no Rust change and no pin change.

AgentCursorVisibilityResolver.resolve(target:in:) keeps its signature. It encodes {target, snapshot} and calls cmux_layout_reducer_json("agent_cursor_visibility") through the new raw-bytes FocusReducer.call(_:json:), then decodes the answer into AgentCursorVisibility. The encoder and decoder write non-finite numbers as the strings the Rust wire reads ("inf", "-inf", "nan"). So a snapshot with a converted CGRect.null or an infinite scale keeps the result the Swift rules gave it.

What stays in Swift:

  • The value types the App builds and reads.
  • placement(forWindow:).
  • The App's snapshot builder, which reads AppKit views.

The anchor's wire name is FFI glue in AgentCursorAnchor+FFI.swift. The debug socket (DebugAgentCursor) uses it instead of its own copy.

Tests

CmuxNextAppTests/AgentCursorVisibilityVectorTests replays schemas/agent-cursor-visibility/vectors.json through the Swift call, plus non-finite cases, the page scale, every anchor name and the old placement tests. It is in an existing target, so no new test target is added.

  • The suite's expectations are the old behavior: on the never-merge ref rust-migration/cursor-visibility-oldswift2 (9d8044946c8, this suite with the old Swift resolver restored), step 4fc11b4a001e4f251721ef3f (aws-m4pro-7) passed, 7 tests.
  • At this head, through the Rust call (proof-step.py green): step 74648ca9a57684eca6250a8c passed, 7 tests in 1 suite, at 60cb287dbe1 (Xcode 26.6). Earlier, at b240e38b11f: step 4029334b707cd63cf4b45d5e (aws-m4pro-6) passed.

AgentCursorVisibilityResolveBench (off unless CMUX_AGENT_CURSOR_VISIBILITY_BENCH=1) times one resolve per vector. The A/B runs it alone on an exclusive host, once with the head's resolver and once with the old Swift resolver restored.

Line accounting (swift-logic-lines.py, merge base ba780613748 → this head)

rowbasehead
swift_logic_lines416313416222 (-91)
mirror_lines7068670712 (+26)

Swift rules deleted: the resolver's 91 LOGIC lines. Its row in swift-logic-classes.tsv is now PLATFORM, because what is left only encodes, calls and decodes. The new extension AgentCursorAnchor in AgentCursorAnchor+FFI.swift is a hand copy of the Rust Anchor::as_str and parse, so its row is MIRROR (+26), like DaemonCapability. app_lines -10 (DebugAgentCursor).

Changelog

none

Observed / Not run

Observed:

  • Compile-check at 440584d687f: 3bcc994ff74702812b1e11a1 failed. The new AgentCursorAnchor+FFI.swift extension took the target's default MainActor isolation, and the resolver's nonisolated decode could not call init?(wireName:). b240e38b11f makes the extension nonisolated, like the enum. Compile-check at b240e38b11f: d99951c21ebc1bb900b1e8db passed. Compile-check at 60cb287dbe1 (this head): 6c63ea2664c88143b26396a8 passed. Earlier Swift compiles of the same Swift half: 70465d8a5869b43387570aa6 passed (1b9e3c972de); e8211715de251bc8ba17f73e passed (old-rules ref).
  • Old Swift rules under this suite: step 4fc11b4a001e4f251721ef3f passed (above).
  • The Rust rules, their red/green tests and the C ABI test are in #31934 (merged).
  • Review receipts: SHIP at b240e38b11f (comment 6573462111), SHIP at 60cb287dbe1 (comment 6573857414).
  • prepush-gate.py: no new violations.

Perf A/B (AgentCursorVisibilityResolveBench, release build, one resolve per vector; a class exclusive measurement step per arm on cmux18-perf). Before = the old Swift resolver restored from feat ba780613748; after = this head. Only rounds whose two arms both ran on cmux18-perf count:

roundarmjobp50p95p99max
1beforefadbace5a2fd5ae64c63cdf2no result: the release test build crashed the Xcode 26.6 optimizer in CloudStartSessionAsksTests, a base red that #32069 fixes
1afterd620f0ccd432f5526051b067cancelled before it started (the same build would crash)
2before(mini scheduler, spec rmcv-b9-ab-before-r2)PENDING
2after(mini scheduler, spec rmcv-b9-ab-after-r2)PENDING

Not done (Rust follow-ups, backlog B9b):

  • The debug socket's snapshot_json (DebugAgentCursor.swift) encodes with a plain JSONEncoder, so a snapshot with non-finite numbers gives "".
  • cmux-app-ffi builds serde_json without float_roundtrip, so a Swift number with 17 significant digits can parse 1 ULP off in Rust (under 1e-13 pt).
  • NaN rects: Rust f64::min/max skip NaN, while CoreGraphics passes it through. The app builds no NaN rect.
  • The panic step (cmux-tui-rust-check.sh panic): not applicable. This PR changes no Rust; the Rust half and its checks are #31934.

Not ready-to-land until the release perf A/B (both arms on one mini) is in this body. It waits for #32069, which fixes the release build.