feat(ribbon): run the proxy overlay on every display as the only motion system
Why
The proxy overlay from #150 only ran with one display. With two displays every motion jumped, drags had no placeholder, and the per-frame Accessibility animation stayed as a second motion system. This makes the overlay the only motion system on every display, and fixes the multi-display problems found while testing it on a real two-display desk.
What changed
- One overlay per display. Each display presents, lands and hides its own borders on its own, exactly as a single display does. A display where nothing moves presents nothing.
- The per-frame Accessibility animation is deleted, with
animation.duration_msandanimation.window_proxies. Native windows get one write per motion. Without Screen Recording, with Reduce Motion, or withanimation.enabled = false, windows move at once. - Hidden windows park in a corner of their own display, chosen so the parked frame overlaps no other display. The model accounts for where macOS really keeps a window.
- The display staircase stays and is hardened. Defi never adopts its own staircase as the user's desk. When only the staircase is known, status shows
displayArrangement=ambiguousand nothing is routed. - Window close is detected as fast as on main. The moving flag clears when the spring stops, and a closed window counts as landed.
- An app that enforces its own size or position converges instead of being re-submitted forever. Width bounds are the window's own width, rounding never asks below the minimum, and a deferred check no longer resets the mismatch history. This idle loop also exists on main.
- A window moved to a taller display moves before it grows, so macOS no longer clamps its height to the smaller display.
- The test suite is smaller and deterministic. Desktop tests use Defi-owned fixture windows only, behind one preflight, with a separate two-display lane. Unit tests were merged into tables, and tests that restated constants or internals were removed.
script/test_coverage.pyreports coverage per module.
Scope
Motion, parking, display arrangement, border hiding during motion, width learning for app-enforced sizes, and the test suite. Overview rendering, keyboard bindings and workspace routing rules are unchanged.
Tradeoffs
- A single step settles in about 0.24 to 0.30 s on the spring instead of the old 125 ms duration. Rapid commands keep their velocity.
- Without Screen Recording there is no animation. This is intended.
- A drop on another display lands at the end of the ribbon. Dropping at the pointer is in #147.
- At rest the daemon uses about 1.5 % CPU, all in window discovery polling. The loop that cost 3.7 % is gone.
- The first landing of a new window whose size limit is not reported yet can still wait about 1 s.
Blast Radius
Every animated motion, parking of hidden windows, multi-display pointer routing and the display arrangement file. Config files that still set duration_ms or window_proxies keep loading, since unknown keys are ignored.
Verification
python3 script/verify.py localpassed on the final commit.python3 script/verify.py desktoppassed on the final build: 11 tests on two displays, session restored, one daemon before and after. The rewritten desktop suite passed three full runs in a row earlier on the branch.- On the real desk: no parked window or partial column touches the other display, the pointer crosses the desk edge, and stopping Defi restores the side-by-side arrangement.
- With a fixed-width window and System Settings managed, 30 s at rest show 0 re-submits,
settling=0anddrift=0[]. Workspace switches fade in 334 to 348 ms with no landing timeout. - Window close latency matches main: median 480 ms versus 486 ms over 6 closes each.
[!NOTE]
Run the ribbon proxy overlay on every display as the only motion system
- Replaces the single shared presenter and panel with one proxy presenter, overlay panel, and motion token per display in RibbonProxyRenderer.swift, RibbonProxyPresenter.swift, and MacOSPlatform.swift. Each display independently decides whether its overlay moves, and carries, background captures, and border hiding are display-scoped.
- Removes the native Accessibility animation pipeline: interpolated frames, animation lanes, retargeting velocities, spring clock types in Animation.swift, and the staged write path in AXFrameCoordinatorWrites.swift are deleted. Frame writes now apply final positions and sizes directly and verify after a short fixed delay.
AnimationConfig in ConfigModels.swift keeps only enabled; duration_ms, window_proxies, the settings search entry, and the appearance UI control are removed. Workspace transitions, mouse drops, and lifecycle reflows now animate purely from the enabled flag.
- Layout width constraints now count inter-window gaps outside each window's own width in Layout.swift, and parking moves to the display-relative
parkedFrame utility in Parking.swift with new display-staircase detection in DisplayArrangement.swift.
- Adds verification tooling: source-coverage reporting in test_coverage.py, not-run desktop lane accounting plus a daemon-count guard in verify.py, and launch retry in desktop_session.py. Many test suites are consolidated into parameterized cases.
- Risk: configs that used
duration_ms = 0 to get instant movement now animate whenever enabled is true; check AnimationConfig decoding in ConfigModels.swift and callers in DaemonAnimation.swift. Public API removals in Animation.swift, AXFrameCoordinator, and FrameCommit (spring samples, lane plans, ParkingSide, resolveParkingPlacement) break any out-of-tree consumers.
Macroscope summarized bab2202.