NOOP version
11.8.0 (build 198), built from main at c8a3f10
Platform
macOS
OS version
macOS 27.0 (26A428)
WHOOP strap / Oura ring model
Not strap-dependent. Decorative UI rendering, reproduces with the app simply left running.
Where the data is coming from
Not applicable.
Test Centre profile
Not a test-mode bug
What happens
The windowObscured gate added in #2394 (merged as c8a3f10) never becomes true, so the three TimelineView drivers it was meant to stop keep running when the window is hidden or miniaturised. #2393 is therefore still open in practice on main.
I posted this on #2394 about forty seconds after it merged, so it is easy to miss there. Filing it separately so it is tracked.
Measurements
Built from the PR head 38ba6e2, whose refreshWindowObscured is byte-identical to main today. Xcode 27.0, macOS 27.0, -configuration Release, arm64, MacBook Pro 18,3 (M1 Pro, 16 GB). CPU-time deltas over 90 s windows:
| State |
CPU |
| visible |
23.73 % |
| hidden (Cmd+H) |
28.53 % |
Hidden still costs more than visible, which is the original symptom. A sample taken while AppKit reports the process as visible: false still contains 37 LiquidRender frames, plus RB::SurfacePool::collect and CABackingStoreCollect.
Cause
canBecomeMain is not a property of a window's kind. It reflects the window's current state, and a hidden or miniaturised window cannot become main, so the real window drops out of windows at exactly the moment the gate should close. windows then goes empty, and the deliberate empty-list rule reads that as "not obscured".
I added logging to refreshWindowObscured and to the five observers. Every notification fires correctly. The recompute:
visible all=3 cbm=1 obscured=N
hidden all=4 cbm=0 obscured=N <- should be Y
minimised all=4 cbm=0 obscured=N <- should be Y
Per window while hidden:
'NOOP' class=AppKitWindow cbm=N titled=Y vis=N occVis=N
'Item-0' class=NSStatusBarWindow cbm=N titled=N vis=Y occVis=Y
The same AppKitWindow logs cbm=Y while visible.
So the two design decisions in #2394 cancel out in the one case the fix targets. Both are individually reasonable. The filter exists to drop the MenuBarExtra, and the empty-list rule exists so the app does not show a frozen first frame during launch.
Everything else in the fix works. During a window restore I caught a transient cbm=1 onScreen=N obscured=Y, so the observers, the recompute and poseStill are all correct. Only the filter predicate is wrong.
A predicate that holds
styleMask.contains(.titled) does both jobs. It excludes NSStatusBarWindow, which reports titled=N in every state I logged, and unlike canBecomeMain it does not change when the window is hidden or miniaturised. I computed both in the same run, so this is measured rather than proposed:
| State |
cbm count |
titled count |
obscured now |
obscured with titled |
| visible |
1 |
2 (one on screen) |
N |
N |
| hidden |
0 |
2 (none on screen) |
N |
Y |
| minimised |
0 |
2 (none on screen) |
N |
Y |
One caveat I could not close: the window list also holds SheetPresentationWindow entries reporting titled=Y. In everything I observed they track the main window's visibility, so they do not change the outcome, but they would be counted and I did not test with a sheet actually open.
What I expected instead
Hiding the app or sending it to the Dock should make poseStill(_:) return true and stop the liquid timelines, the way the in-app "Reduce motion in NOOP" toggle already does.
Steps to reproduce
- Build
main for macOS and launch it with "Reduce motion in NOOP" off.
- Read
ps -p $(pgrep -x "NOOP Staging") -o time= twice, 90 s apart, divide the delta by 90.
- Hide with Cmd+H, wait 20 s, measure again. It does not drop.
Note on the tests
QuietMotionCoverageTests pins the literal string canBecomeMain to guard against the filter being dropped. That assertion passes today while the gate does nothing. A source-text census cannot catch a predicate that is correct in one window state and wrong in another, which is what happened here.
I can rerun any of this against a revised push, or test a different predicate if you would rather pick one.
NOOP version
11.8.0 (build 198), built from
mainatc8a3f10Platform
macOS
OS version
macOS 27.0 (26A428)
WHOOP strap / Oura ring model
Not strap-dependent. Decorative UI rendering, reproduces with the app simply left running.
Where the data is coming from
Not applicable.
Test Centre profile
Not a test-mode bug
What happens
The
windowObscuredgate added in #2394 (merged asc8a3f10) never becomes true, so the threeTimelineViewdrivers it was meant to stop keep running when the window is hidden or miniaturised. #2393 is therefore still open in practice onmain.I posted this on #2394 about forty seconds after it merged, so it is easy to miss there. Filing it separately so it is tracked.
Measurements
Built from the PR head
38ba6e2, whoserefreshWindowObscuredis byte-identical tomaintoday. Xcode 27.0, macOS 27.0,-configuration Release, arm64, MacBook Pro 18,3 (M1 Pro, 16 GB). CPU-time deltas over 90 s windows:Hidden still costs more than visible, which is the original symptom. A
sampletaken while AppKit reports the process asvisible: falsestill contains 37LiquidRenderframes, plusRB::SurfacePool::collectandCABackingStoreCollect.Cause
canBecomeMainis not a property of a window's kind. It reflects the window's current state, and a hidden or miniaturised window cannot become main, so the real window drops out ofwindowsat exactly the moment the gate should close.windowsthen goes empty, and the deliberate empty-list rule reads that as "not obscured".I added logging to
refreshWindowObscuredand to the five observers. Every notification fires correctly. The recompute:Per window while hidden:
The same
AppKitWindowlogscbm=Ywhile visible.So the two design decisions in #2394 cancel out in the one case the fix targets. Both are individually reasonable. The filter exists to drop the
MenuBarExtra, and the empty-list rule exists so the app does not show a frozen first frame during launch.Everything else in the fix works. During a window restore I caught a transient
cbm=1 onScreen=N obscured=Y, so the observers, the recompute andposeStillare all correct. Only the filter predicate is wrong.A predicate that holds
styleMask.contains(.titled)does both jobs. It excludesNSStatusBarWindow, which reportstitled=Nin every state I logged, and unlikecanBecomeMainit does not change when the window is hidden or miniaturised. I computed both in the same run, so this is measured rather than proposed:cbmcounttitledcounttitledOne caveat I could not close: the window list also holds
SheetPresentationWindowentries reportingtitled=Y. In everything I observed they track the main window's visibility, so they do not change the outcome, but they would be counted and I did not test with a sheet actually open.What I expected instead
Hiding the app or sending it to the Dock should make
poseStill(_:)return true and stop the liquid timelines, the way the in-app "Reduce motion in NOOP" toggle already does.Steps to reproduce
mainfor macOS and launch it with "Reduce motion in NOOP" off.ps -p $(pgrep -x "NOOP Staging") -o time=twice, 90 s apart, divide the delta by 90.Note on the tests
QuietMotionCoverageTestspins the literal stringcanBecomeMainto guard against the filter being dropped. That assertion passes today while the gate does nothing. A source-text census cannot catch a predicate that is correct in one window state and wrong in another, which is what happened here.I can rerun any of this against a revised push, or test a different predicate if you would rather pick one.