Skip motion while the display sleeps so inserted rows show
Summary
In cmux NEXT, a sidebar row inserted while the display sleeps never finished its open animation. A sleeping display draws no frames, so the AppKit animation never advanced and the row stayed at alpha 0 and zero height until Reduce Motion was turned on. A row already opening or closing when the display went to sleep stopped where it was in the same way, and a closing row's completion (which removes it) never ran.
Two changes in CmuxNextDesign:
Motion.canAnimate(in:)returns false when the window's display is asleep, so a change started there applies at once and its completion runs now. This covers everyMotion.animate/animateTimed/animateExitcaller.CGDisplayIsAsleepfor the window's display decides. That covers a launch while the display already sleeps, and a sleep notification with no matching wake can't leave later changes snapping.- A change already running when the display goes to sleep ends at once only if it opts in:
Motion.animate(_:in:_:settleAtSleep:completion:).MotionInFlighttracks those until their completion runs. OnscreensDidSleepNotificationorscreensDidWakeNotification(for one that froze in the gap), each one'ssettleAtSleepruns in a zero-length group and its completion runs.settleAtSleepapplies the current target, read when it runs, never the values the animation captured. A completion runs exactly once, fromfinish()or from AppKit, whichever comes first. An entry 2 s past its expected end is no longer settled, but AppKit's completion for it still runs.
Only the sidebar row move and open (RowMotion.run, RowMotion.place) opt in. Their settle is list.reload(animated: false), so rows take the places the list holds at that moment, and the move's completion then recycles the rows still leaving. Every other caller keeps the skip at start and is never replayed. That includes the overlay fade, the viewport scroll, SidebarRegionView.relayout and the constraint animators. Their change blocks don't escape. CA-based helpers (Motion.set, layer actions) set model values first; CADisplayLink users are covered by FrameScheduler's stall timer. Window occlusion isn't part of the check: a freshly ordered window can report itself occluded for a moment, and automation launches keep occluded windows drawing on purpose.
The first commit fixes a base compile red in SpareNewTabChatOpensTabsTests (AgentTabStore(tag:) is now AgentTabStore(build:)); without it no CmuxNextAppTests suite builds.
Proof
SidebarInsertWhileDisplayAsleepTests (CmuxNextAppTests) runs a sidebar in an on-screen window. The tests are gated on an awake display with animations on, so a sleeping or speed-off host skips them instead of failing. Every run below is from a display host (aws-m4pro-7/8/9), not a headless skip.
aRowInsertedWhileTheDisplaySleepsShowsWhole: with every display reading as asleep, a new row is at alpha 1 and full height without a frame. Red at ba7ff07b0f3e (test only), step6ef5cd220103bce5f284c867: alpha 0.0, height 0.0.rowsStillMovingWhenTheDisplaySleepsEndAtOnce: with the display awake, workspace 4 opens and workspace 2 closes. The test postsscreensDidSleepNotificationmid-animation and checks the new row is at alpha 1 and full height and the closing row has left the list. Red at 7b088a27c533 (test only), step230d5e998fb6068aebe376d5: alpha 0.0, height 0.0, and the closing row still in the list.rowsANewerReloadPlacedKeepTheirPlacesWhenTheDisplaySleeps: an insert animates, then two reloads without motion remove workspace 2 (its view goes to the pool) and add workspace 5. Sleep must leave every row where the list now places it.aClosingRowLeavesEvenAfterItsReplayExpired: a closing row whose entry has expired still leaves when AppKit ends its animation. Red at f3f938126bac against the global replay (test and theexpirySlackseam only), step2066c44bc67ba9ff9ffa8ca9. Two rows went back to their old slots (y 82 vs 56, y 56 vs 30), and the closed row stayed in the list.aRowInsertedWhileTheDisplayIsAwakeStillOpens: the awake look is unchanged, and the row still opens with motion. It passed in every run.- Green at fce1d0b77f8a: full
run-swift-testing-suites.sh Packages/macOS/CmuxNexton aws-m4pro-9, step1ca0e17c1c0fbd47e250cb1f. All five tests and the whole step passed. - Release compile at fce1d0b77f8a:
scripts/cmux-next/check-release-compile.sh(isolated), stepd8f8a3f0af46db5596e652f9, passed.
Capture: a DEV build of an earlier head (fleet job 9b568f2772b49210064b70d2, before the sleep settle, which doesn't touch the awake look) on a cua-4 capture slot, speed "fast", three workspace.newBelow inserts with the display awake. debug.motion recorded three appkit.move spans of 334 to 350 ms. Sidebar crop only:

Full quality:
A live insert during real display sleep: unverified. Putting a shared capture mini's display to sleep would affect other lanes, so that case rests on the tests above.
Changelog
- Fixed: Sidebar rows added while the display is asleep now show right away instead of staying invisible.
🤖 Generated with Claude Code