R-INPUT S2a: the input router reduces each window's focus as the app's focus handle does (WindowFocus), Rust only
R-INPUT stage S2a (DESIGN.md, stage S2). Stage S2 makes the input router (cmux-focus-core input::InputRouter, #34052) own every window's focus, so that the app's per-window focus handle can become the router's in S2b and S2c. Today the router and the app's handle reduce the same focus event in different ways:
- A topology report: the router runs
reducer::reduceagain. The app's handle takesreducer::set_topology's fast path: an equal topology changes nothing. The daemon sends the topology again after every delta, so the router reportedranfor events that the app's handle drops. - The router had no address bar memory (Chrome's per-tab omnibox focus). The app's handle keeps it beside the state.
This PR makes the router reduce focus as the app's handle does. It changes no caller and no app behavior:
- New
cmux_focus_core::window::WindowFocus: one window's focus (the state, the address bar memory, the Close Tab aim), with the operations that the app's focus handle performs (send,set_topology,pane_moved,frame_presented,pane_emptied,aims_at_emptied_pane,set_close_focus,import,rederive). - The FFI focus handle (
cmux-layout-reducer-ffisrc/focus.rs) now holds aWindowFocusand delegates to it. The C ABI and the reply bytes do not change. - The router holds a
WindowFocusfor each window, and a focus event goes throughWindowFocus::send. After the focus effects, the router emits the newInputEffect::FocusReduced { window, reduction }: whether the reducer ran and what the address bar memory asks for. The app's reply carries the same data. - The fuzz's reference model also holds a
WindowFocusfor each window, so the fuzz compares the routing over the same focus rules.
This is step A of the FFI two-step rule (STATUS 5388): cmux-focus-core and cmux-layout-reducer-ffi are app FFI inputs. After this lands, the bot publishes the FFI release and opens the re-pin. There is no Swift half: the ABI does not change.
Test (cmux-tui/crates/cmux-focus-core/tests/input_router/key_router/window_focus.rs):
key_router::window_focus::the_router_reduces_focus_events_as_the_apps_focus_handle_does: the same focus events go through the router and through aWindowFocus(the app handle's type). The events are a topology, the equal topology again, a mouse focus on a page pane, the address bar by keyboard, a mouse focus on the terminal pane, a keyboard focus back to the page pane, and the return event. The router's focus effects, itsFocusReducedreduction and the window's state must be equal to the handle's after each event.
Red/green, the same filter key_router:: (scripts/ci/cmux-tui-rust-check.sh --os linux):
- Red 0c9fe162e4f4 (the test and
WindowFocus, with the router as on feat), modetest: step 3d17eb26c48f34c41d3ce7b1. 72 tests ran and 1 failed:key_router::window_focus::the_router_reduces_focus_events_as_the_apps_focus_handle_does, on its assertion at window_focus.rs:68, on the equal topology report (leftSome(Reduction { ran: true, .. }), rightSome(Reduction { ran: false, .. })). It failed again when it ran alone. The first red step (bf51f012572b93d9e4bd7045) also ended in failure, but its log is lost: GitHub answers BlobNotFound and the controller has no full log. So I ran it again. - Green d5cb5d44ed68, mode
all: step 28af93a87fd6752c31c2dbd8. fmt passed. Clippy (--workspace --all-targets -D warnings) passed. 72 of 72key_router::tests passed: the new test, the router fuzz (20,000 cases against the reference model),key_router::mutants::every_mutant_is_caughtandkey_router::allocations::a_key_its_release_and_a_bound_chord_allocate_nothing(a key still allocates nothing). - FFI crate:
cmux-layout-reducer-ffiis its own Cargo workspace, so the fleet's Rust check does not build it. The never-merge reffix/r-input-s2a-ffi-proof-never-merge(439a4c88b5e, listed in stopped.txt) adds the crate to the cmux-tui workspace and runs every workspace test, including the FFI crate's Swift-recorded focus corpus (the_rust_focus_rules_reproduce_every_recorded_swift_step) and the handle tests inclose_focus_rules.rs. Step 6203ede8b4514ffe3266189d (modetest, no filter):cmux-layout-reducer-ffi74 of 74 passed,cmux-focus-core133 of 133 passed. The step as a whole failed on 10 tests in crates that this PR does not touch (acpmux3,cmux-agent-pane-policy2,cmux-tui4 and their reruns; for exampleacpmux::hermetic_daemon_guard every_test_binary_runs_in_the_hermetic_environment, which the proof ref's extra workspace member can cause, andcmux-agent-pane-policy::typescript the_page_sends_only_allowed_acpmux_meta). The GitHub checkcmux-tui/crates/cmux-layout-reducer-ffiof this PR also builds the FFI crate in its own workspace, runs clippy-D warningson all targets and runs every FFI test: it passed.
Independent review: SHIP at d5cb5d44ed68f208cc5c60ccde1fc57b1be9ca5a (https://github.com/manaflow-ai/cmux/pull/34929#issuecomment-6624349281). Its three small points go to a follow-up (S2b touches the same files): an FFI handle test of the reply to an equal topology; a reference model that keeps reducer::reduce for topology events, so that the fuzz also proves the fast path changes no routing; and a doc line that the FFI sends a topology only through set_topology.
Observed / Not run
- Observed: the fleet steps above.
- Not run: anything in the app. This is Rust only, and no caller changes until S2b. The S3 gate (KeyRouterBench p99 ≤ 2 µs and 0 allocations per key before any route turns on) does not apply here: this stage turns on no route.
Changelog
none
No capture: Rust-only library code; the app's behavior does not change.