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.
| Press | Widest gap to the right neighbor | Middle origin / width progress at that moment | Right neighbor progress | Pacing holds, left / right neighbor |
|---|---|---|---|---|
| 1 | 50 pt at +60 ms | 0.08 / 0.08 | 0.32 | 8 / 0 |
| 2 | 43 pt at +48 ms | 0.06 / 0.06 | 0.26 | 11 / 0 |
| 3 | 34 pt at +71 ms | 0.36 / 0.35 | 0.50 | 8 / 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::seealso 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
- Recommended: pace edges, not origins.
Each member carries the lag of both edges, read from the shown frame (
min.xandmax.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::seecounts 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. - 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.