Skip to content

fix: prevent window oscillation when moving windows between displays#395

Merged
acsandmann merged 1 commit into
acsandmann:feat/space-actorfrom
strayer:fix/cross-display-move-oscillation
Jun 18, 2026
Merged

fix: prevent window oscillation when moving windows between displays#395
acsandmann merged 1 commit into
acsandmann:feat/space-actorfrom
strayer:fix/cross-display-move-oscillation

Conversation

@strayer

@strayer strayer commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a regression introduced by #392 (feat: space actor, this PR's base) where moving a window between displays makes some apps oscillate rapidly between the two displays instead of settling. It reproduces with both the move_window_to_display shortcut and a manual mouse drag, and is most visible with frame-resisting apps like Zen and Outlook.

Root cause: #392 changed handle_window_server_appeared to call reassign_window_to_authoritative_space for any known window on every WindowServerAppeared event, re-tiling it onto the WindowServer-reported space. Before #392, this event was ignored for known windows.

WindowServerAppeared is the CGS/SkyLight SpaceWindowCreated event (actor/window_notify.rs), which also fires as a side effect of Rift's own SetWindowFrame dragging a window across a display seam — and fires for both spaces while the window straddles the boundary. So Rift treated the echo of its own move as authoritative, reassigned the window, re-tiled it (moving it again), and chased it indefinitely.

Unlike the parallel WindowFrameChanged path (actor/reactor/events/window.rs), which suppresses self-induced moves via the pending-transaction (txid) check, the new appeared → reassign path had no self-move guard. Apps that accept the frame change settle after one event; frame-resisting apps never settle, so they oscillate.

This is not a removed guard — the existing oscillation protections (#342 two-tree desync, floating ping-pong, churn/sleep quarantine) are all intact; they simply don't cover this newly-added path.

Diagnosis from live logs: a 12 s capture of one repro showed the Zen window (WindowServerId(54951)) generating 121 WindowServerAppeared events split almost evenly across both displays (60 on one space, 61 on the other), driving ~52 reassign re-tiles — the window's frame alternating between two origins roughly every 170 ms with no MoveWindowToDisplay command in between.

Fix: in handle_window_server_appeared, skip the reassign_window_to_authoritative_space fallback when a Rift frame transaction is still in flight for the window (transaction_manager.get_target_frame(wsid).is_some()) — i.e. when the appearance is an echo of Rift's own move. The authoritative-space record is still updated, so #392's tested bookkeeping contract is unchanged. Genuine external moves (manual drag with no pending transaction) still reassign and follow the window.

Tests:

  • appeared_does_not_reassign_window_while_rift_move_is_in_flight — the regression guard; fails before the fix (the window is reassigned to the echo space), passes after.
  • appeared_reassigns_window_without_pending_rift_move — ensures genuine external space changes are still followed (guards against over-suppression).

Disclaimer: The root cause analysis and fix were produced by Claude Code (Opus 4.8), guided and verified by the PR author.

Base note: This targets the still-draft #392 branch (feat/space-actor), since the regression originates there. It will need retargeting if #392's approach changes before merge.

Test plan

  • cargo test passes (284 tests, including 2 new tests)
  • New guard test fails without the fix, passes with it
  • Live verification with the debug build: shortcut moves no longer storm (~6× fewer WindowServer events, 0 reassign re-tiles from the appeared path); frame-resisting apps show only a sub-frame commit flash and settle; manual mouse-drag across displays is followed correctly and settles

…displays

PR acsandmann#392 (space actor) changed handle_window_server_appeared to reassign a
known window to the WindowServer-reported space on every SpaceWindowCreated
event. That event also fires as a side effect of Rift's own SetWindowFrame
dragging a window across a display seam, so Rift chased the echo of its own
move and re-tiled endlessly. Apps that resist AX frame changes (e.g. Zen,
Outlook) never settle, producing a ~170 ms flap between displays.

Unlike the WindowFrameChanged path, this appeared->reassign path had no
self-move guard. Skip the reassignment when a Rift frame transaction is
still in flight for the window (transaction_manager.get_target_frame is
Some); the authoritative space record is still updated. Genuine external
moves (manual drag, no pending transaction) continue to reassign.

Adds appeared_does_not_reassign_window_while_rift_move_is_in_flight (the
regression guard) and appeared_reassigns_window_without_pending_rift_move
(ensures genuine external moves are still followed).
@acsandmann acsandmann merged commit cc709c1 into acsandmann:feat/space-actor Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants