Note
🤖 Claude Opus 5.5 responding on behalf of Sam
Triage on main at ffefedfa, engine a20f56e.
The issue's description of the current behavior holds in the code.
One acceptance criterion is ambiguous, so I added needs-info with one question for Sam below.
Root cause
A swipe can only ever move one strip, the strip that ActiveWorkspaceMarker marks, and that marker follows the menu bar display, not the pointer.
I read this from the code.
I did not observe it on hardware, and I could not run an engine probe test, because the Mac's data volume had 196 MB free.
- The event tap drops the pointer location.
Event::Swipecarries onlydelta,fingers, anddistance_cm(input.rs#L734).Event::Scroll, the modifier-wheel path, carries onlydelta(input.rs#L652). swipe_gesturetakesSingle<SwipedStrip, With<ActiveWorkspaceMarker>>andActiveDisplay(scroll.rs#L329). Both swipes and modifier-wheel scrolls go through it.ActiveWorkspaceMarkeris on exactly one strip across all displays (workspace.rs#L537).ActiveDisplayMarkermoves only on theactiveDisplayDidChange:notification (display.rs#L443), andactive_display_idis the menu bar display (manager.rs#L140). Withfocus_follows_mouseoff, the v1 default, moving the pointer does not move the menu bar.
So with focus on display A and the pointer on display B, a swipe moves A's strip. The fault is the target choice, not one line.
What the change touches
The whole swipe pipeline assumes the swiped strip is the active strip on the active display.
Each of these reads ActiveWorkspaceMarker or ActiveDisplay:
swiping_timeout,settle_strip,apply_snap_force,scrolling_integrator, andapply_scrolling_constraintsinsrc/ecs/scroll.rs. They take the viewport width from the active display, which is wrong for a strip on a display of another size.finish_settle_on_deactivateinsrc/ecs/scroll.rslands a settle at once when the strip loses the active marker.window_frame_updateinsrc/ecs/systems.rschecks for a running swipe on the active strip only.
My proposed shape, which the builder can change, is to choose the target strip once, on TouchpadDown or the first wheel tick, and mark that strip with a component.
The pointer target uses the strip for the current Space of the display under the pointer.
The focused target uses ActiveWorkspaceMarker, as today.
The swipe systems then read the marked strip and its parent display, not the active strip and active display.
The tap can read the pointer from the gesture event's location, and the mock cursor_position lets engine tests place it.
Question for Sam
After a swipe ends, settle_strip focuses the last-focused window in the column it lands on (scroll.rs#L718).
With the pointer target, that moves keyboard focus and the menu bar to the swiped display.
Criterion 1 says the other strip "keeps its focus", which can mean either option below.
- Option 1 (recommended). The swipe focuses a window on the swiped display, the same way a swipe on one display focuses its landing column today. The other strip keeps its scroll position and its last-focused column, so focusing back returns to the same window. I recommend this because it keeps one swipe behavior on every display.
- Option 2. A swipe on a display without focus only scrolls, and focus stays where it was. The swiped strip then shows a column whose window does not have focus.
Proposed wording for criterion 1 under option 1: "With two displays, a swipe moves the strip on the display under the pointer and focuses its landing column. The other strip keeps its scroll position and its last-focused column."
Defaults I would apply unless Sam says otherwise
- The target setting covers modifier-wheel scrolling too, because both inputs share
swipe_gestureand release scope gives them the same settle. - Keyboard commands keep acting on the focused strip, as the issue says.
How each criterion gets checked
| Criterion | Check |
|---|---|
| 1, 3, 4 | Engine tests with two displays and a mock pointer, then Sam's real-trackpad check S-1. Synthetic swipes do not act like real fingers, so an agent cannot drive these. |
| 2 | verify-miri opens Settings > Gestures, picks the focused-strip target, and reads the key back from config.toml. |
| 5 | Docs review in the PR. |