Desktop shows beside a column while it changes width#308

opened4d agobySSamanyu Das

Steps to reproduce

  1. Build Miri Developer from main at 6edff137 (engine ee89647) with animations on and Reduce Motion off.
  2. On the built-in display, open three resizable windows in one strip, each 665 pt wide with 8 pt gaps, and focus the middle one.
  3. Press Control-Option-R once, so the middle column widens to its next width preset (1012 pt).
  4. Sample each window's frame from the window server about every 1 ms during the change.

verify-miri run 20261106-103819 did this with three AXFixture windows: 6 presses on main, and 6 on the #280 fix branch for comparison.

Expected result

The neighbor to the right moves with the widening column's right edge. No more desktop shows between the two windows than their 8 pt rest gap at any moment of the change, as during a strip scroll.

Actual result

  • On all 6 presses on main, desktop showed between the middle column and its right neighbor for most of the change. It showed from about +30 ms to between +325 and +384 ms after the key, up to 74 pt wide (widest per press: 42, 70, 41, 60, 69, 74 pt).
  • The pair to the left of the middle column never opened past its rest gap. The middle column overlapped it by up to 81 pt instead.
  • The #280 fix branch (engine f953d04) opened the same gaps: widest per press 52, 78, 72, 47, 50, and 68 pt.
  • During focus moves that scroll the strip, no gap grew past its rest gap on either build.
  • No window became unreachable or off-screen.

Acceptance criteria

  • With the steps above, no sample shows more than the 8 pt rest gap between the widening column and its right neighbor. Check with the same window-server sampling, over 6 width presses.
  • When the column narrows to a smaller preset, no sample shows more than the 8 pt rest gap to the neighbor on either side. Check with the same three AXFixture windows and sampling, over 6 presses of Control-Option-R from the largest preset, which wraps to the smallest. This covers a narrowing window that shows its change later than its neighbors, as an AXFixture window does. A right neighbor that shows its moves later than the narrowing window is out of scope, because Sam chose option 1 of the triage comment on 6 November 2026.
  • During a strip scroll, no gap grows past its rest gap, as before, and the engine's row pacing and shared-edge tests pass.
  • With Reduce Motion on, or Animate window movement off, a width change still happens in one step.

Miri version or commit

6edff137

macOS version and Mac model

macOS 27.0.1 (26A434), MacBook Air M3

Displays and arrangement

Built-in display (60 Hz) left of a 27-inch external display (100 Hz), separate Spaces on. The run used the built-in display.

Diagnostics or logs

text
1Window-server samples on main, one Control-Option-R press on the middle column (665 -> 1012 pt):
2 widest gap to the right neighbor per press: 42, 70, 41, 60, 69, 74 pt (rest gap 8 pt)
3 gap past rest from about +30 ms to +325..384 ms after the key
4Evidence: ~/.cache/miri/verify-miri/runs/20261106-103819/resize-main1, resize-main2

Note

🤖 Claude Opus 5.5 responding on behalf of samanyudas

Triage: reproduced on the current main engine, and the root cause is in row pacing. Pacing holds a column only by its left edge, so it never sees the right edge that a width change moves.

Reproduction on the current main engine

verify-miri run 20261106-191756 used Sam's running build at main 471737fb, engine a20f56e. Current main (ffefedfa) differs from it only in docs. The run used three AXFixture windows on the built-in display and made 3 Control-Option-R presses on the middle one (665 to 1012 pt), with RUST_LOG=info,paneru::ecs::pacing=debug.

PressWidest gap to the right neighborMiddle origin / width progress at that momentRight neighbor progressPacing holds, left / right neighbor
150 pt at +60 ms0.08 / 0.080.328 / 0
243 pt at +48 ms0.06 / 0.060.2611 / 0
334 pt at +71 ms0.36 / 0.350.508 / 0

Progress is the fraction of the way from the start frame to the end frame, as the window server shows it. The 12 presses of run 20261106-103819 show the same pattern. At each widest gap, the middle window's origin and width were 0.07 to 0.38 of the way, and the right neighbor was 0.30 to 0.63 of the way.

Root cause

  • The widening window is the slow one. Its app takes each size write as a slow accessibility round trip, and its writer keeps only the newest frame (src/manager/window_writer.rs). So the window shows the change late, with its origin and width in step with each other. This is the legitimate slow-window state that pacing exists to handle.
  • pace_row (src/ecs/pacing.rs) measures each column's lag only at its origin (Member::lag, min.x). A column that moves right is held only when the column on its left has a negative origin lag, meaning that column has yet to move right.
  • When a centered column widens, its origin moves left, so its origin lag is positive. As a result, the right neighbor is never held, and it eases at the engine's pace while the widening window's right edge trails behind. The debug log confirms this: "pacing holds window" lines name only the left neighbor.
  • The left neighbor is held, because the column's left edge is exactly what pacing reads. That is why the left side only overlaps and never opens a gap.
  • During a strip scroll, every column only moves, so the lag at the left edge equals the lag at the right edge, and no gap opens.
  • Tracked::see also compares only the origin. So a window whose origin shows on time but whose size shows late would count as prompt.

Fix options for Sam

  1. Recommended: pace edges, not origins. Each member carries the lag of both edges, read from the shown frame (min.x and max.x) against its target frame. The rightward pass reads the right-edge lag of the column behind, and the leftward pass reads the left-edge lag. Tracked::see counts a window as late when either edge shows away from where the engine asked for it. This covers widening on both sides, and narrowing when the resizing window is the slow one.
  2. Option 1, and also hold the width. When the neighbor is the slow one during a narrowing, its gap can only close if pacing also holds the resizing column's shrinking edge, which means writing its width, not only its position. This is a prediction from the code: this run did not test narrowing with a slow neighbor. It covers acceptance criterion 2 in every case, but it adds width holds to pacing.

The first commit of either fix would be a failing pace_row unit test for a column that widens about its center.

Note

🤖 Claude Opus 5.5 responding on behalf of samanyudas

Decision: Sam chose option 1 from the triage comment on 6 November 2026. Row pacing will track both edges of each column.

The fix covers these changes in src/ecs/pacing.rs:

  • Each pacing member carries the lag of its left edge and its right edge, from the shown frame (min.x, max.x) against its target frame.
  • The rightward pass in pace_row reads the right-edge lag of the column behind. The leftward pass reads the left-edge lag, as now.
  • Tracked::see counts a window as late when either edge shows away from where the engine asked for it.

Pacing will still not hold a column's width (option 2). So a right neighbor that shows its moves later than a narrowing column can still open a gap. That case is a prediction from the code. No run has shown it.

I rewrote acceptance criterion 2 to match this scope. It now names the narrowing check (6 presses of Control-Option-R from the largest preset, with the same sampling) and marks the slow-right-neighbor case as out of scope. Criteria 1, 3, and 4 are unchanged.

The first commit of the fix will be a failing pace_row unit test for a column that widens about its center. Implementation is not started. It will run in its own session.