Focus rules move into cmux-focus-core with app FFI calls: close successors, Close Tab aim, address bar memory, input invariants (A12 step A)
Check the app FFI pin: expected, step A of a two-step FFI move.
Summary
Step A of rust-migration backlog A12: the Rust half only. The focus rules the macOS app keeps in Swift beside cmux-focus-core move into that crate, with their Swift tests ported as Rust tests, and the app FFI (cmux-layout-reducer-ffi, linked by cmux-app-ffi) gets the calls. No Swift change and no behavior change: no Swift code calls the new symbols yet. Step B (the Swift half, held in #31835) makes Swift call these rules and deletes the Swift copies after the bot re-pins CCmuxAppFFI on feat.
| Swift rule (deleted in step B) | Rust |
|---|---|
CmuxNextDesignTokens/CloseFocus/FocusAfterClose.swift tab, openerFamily, liveOpeners, workspace | close_focus::{tab, opener_family, live_openers, workspace} |
CmuxNextFocus/PaneCloseAim.swift | close_aim::PaneCloseAim |
CmuxNextFocus/OmnibarFocusMemory.swift | omnibar_memory::OmnibarFocusMemory |
CmuxNextFocus/InputInvariants.swift, InputInvariants+World.swift | invariants::{model, transition, step, world, expected_responder} |
FocusState+TypingTab.swift, FocusState+GhosttyAgreement.swift, Resolved.kind | FocusState::{typing_tab, terminal_tab}, Resolved::{pane, tab, kind, is_text_input, ghostty_mismatch} |
The existing pane rule stays the one pane rule. It is now generic over the id type, with no behavior change. The layout.closeFocus value keeps one parser: the app's settings layer.
The app FFI:
- Per close, typed (no JSON, no handle):
cmux_close_focus_tab,cmux_close_focus_workspace,cmux_close_focus_live_openersover the caller'su32ids. - Per press and per reduction, on the existing window focus handle:
cmux_focus_pane_moved,cmux_focus_frame_presented,cmux_focus_pane_emptied,cmux_focus_aims_at_emptied_pane. The handle notes the address bar memory on everycmux_focus_sendandcmux_focus_set_topology. The reply's two formerreservedbytes are nowomnibar_returnandreturns_to_omnibar(same layout), and atyping_tabstring follows the reply's last field. The pinned Swift reader reads the reply by field, so the added fields do not change what it reads. - Debug only, JSON channel:
focus_input_world,focus_input_step,focus_debug_window.
One deliberate difference from the Swift rules: past its 64-tab bound, the address bar memory drops the oldest tab. The Swift Set.removeFirst() dropped one in hash order, which no rule relied on; the Rust order is deterministic and tested (ported::omnibar_memory::the_memory_is_bounded_oldest_first). A full memory also evicts only when it adds a new tab; the Swift copy evicted one even when the tab was already remembered. It takes effect in step B. Violation detail strings are diagnostics and follow the Swift text where it is plain; the invariant ids and windows are the Swift ones.
Tests
The Swift tests of these rules are the Rust corpus (cmux-tui/crates/cmux-focus-core/tests/focus_rules, filter ported::): FocusAfterCloseOpenerTests, the deleted FocusAfterCloseModelCheckTests (exhaustive pane model check for both policies with its 7 mutants, tab check with its 2 mutants, workspace check), the focus cases of plans/cmux-next/formal/closefocus.tla (strip, strip-recent and list configurations: SuccessorRule, C1-C3, and the "history" mutant), PaneCloseAimTests, OmnibarFocusMemoryTests, ChromiumPageFocusInvariantTests, GhosttyFocusAgreementTests, plus one case per F, T and W invariant and the app's InputObservation JSON. cmux-layout-reducer-ffi/tests/close_focus_rules.rs drives each new C call and JSON op (also under ported::).
Red first: commit 99164cab0b3 adds the tests with stub rules; b823d9258c9 moves the rules.
- Red:
cmux-tui-rust-check.sh test ported::(Linux) on the same commit as first pushed (5efae6086e7, before the rebase onto the current feat), fleet job 94d94b1d7c3c98a3d2d076ef: 54 tests run, 38 failed (every moved rule), 16 passed (the pane rule cases, which were already Rust, and the mutant checks). - Green, same Rust tree on the #31835 branch: 75ced4de22c's proof ref 7551cbe754b,
test ported::6e96e84bf8ace9b8d95dcf1c: 62 of 62 passed (both crates); clippy ece2b45de78af2a18cbd0a31: passed. Earlier: the recorded focus corpus (test swift_step, which covers the reducer's call of the now generic pane rule) feaebcc95776d0be3f5c0775: passed. - At head 022c23e8acc (cmux-bot merged feat 203c0ff939e; it brings #32055's RestoredPane change into
reducer.rs, merged with no conflict; the PR's own changed lines are identical): panic 1c19eec6c8ed0135ec9ef983; at proof refrust-migration/focus-rules-step-a-ffiproof4(3a3f051cd1c):test ported::01bd553ba5b5062f582bca06,test swift_step3bf9b6fd19b8d9e5cf342bac,test restored_panebbc499e34a79f1cb9ea3685e,test a_record_4d4a6a78aef86ec22a9904a9 (feat's new focus tests, on the merged reducer), clippy 0ca400d127c369e53e03b5b7: pending. - At head bd041631bcc (my merge of feat 48d5d8e1527, 3 conflicts resolved by keeping both sides): base reds, 0 own hits.
- Panic: head ad551fa3c7cef60a95185603 and base (merge base 48d5d8e1527) 1fabb19b87f573b04dce0fcc both RED on the same rows only,
cmux-tuiindexing_slicing 835 > 831 and string_slice 84 > 82 (from #28950; fix #32128). At the head thecmux-focus-corecount goes down;cmux-layout-reducer-ffiis outside the ratchet's workspace. No hit of this PR. - Clippy 85b41ce1f79a47215764f5b4: base findings only, none in this PR's crates:
cmux-tui/crates/cmux-tui/src/link/mod.rs:515too_many_arguments (8/7; the 8th parameter came with 7ea60b1b546, #28950),cmux-tui/crates/cmux-tui/src/cli/app/tests.rs:352unused_qualifications (the line came with 7ea60b1b546, #28950; #32065 last changed the file). test ported::(both crates) ad976b9ee665b7cbe839db74: 62 of 62 passed.
- Panic: head ad551fa3c7cef60a95185603 and base (merge base 48d5d8e1527) 1fabb19b87f573b04dce0fcc both RED on the same rows only,
- At b7bd91b444b: panic 5072fd63d52e5b5e6912c1d8 passed (no new hit);
test ported::a216cb1eba1155faabfb09ea 62 of 62; clippy 9caa50875e72722d36852436 passed;test swift_step53fa42dbc84016962b6fb58f passed.
The proof refs are this head plus one never-merge commit that builds cmux-layout-reducer-ffi inside the cmux-tui workspace: the crate is its own Cargo workspace, so the fleet script does not build it otherwise.
Observed / Not run
Observed: the fleet jobs above; the prepush gate passed.
Not run: any Swift build (no Swift changes here). Swift suites, the perf A/B and the line accounting belong to step B (#31835).
Earlier review: an independent review passed the same Rust at 75ced4de22c on #31835. A new review at this head follows.
Changelog
none