"The lab observed hide() returning false while the target processed its DoHide event. Judge the same process's resulting state, not this immediate return value." — a comment in Tinker's visibility adapter
A Boolean That Means "Maybe"
NSRunningApplication.hide() returns a Bool, and the natural reading is "true means hidden, false means failed". Tinker's lab disproved that reading on real apps. Hiding another process is not a function call into that process; it is a request delivered to it, which the target app handles on its own event loop. The lab saw hide() return false while the target was still processing the hide event — and then the app hid anyway. Taking the return value at face value would have recorded a failure for a change that had in fact happened, which in Tinker's terms is the worst kind of lie: an uncertain receipt that was not uncertain, or worse, a dropped one.
Ask the Process, Not the Call
So the adapter keeps the return value only for its error message ("request accepted" or "request not acknowledged") and decides the outcome by observation: it re-finds the same process by its three-fact identity and reads isHidden. If the process has vanished mid-change, that is an error — and because the request had already been made, it leaves the receipt uncertain. If isHidden matches the wanted value, the change is confirmed.
Why It Polls, and Why Every 15 ms
There is a second subtlety. AppKit caches isHidden and similar properties for a running application and refreshes them on the main run loop, so reading the property twice in the same turn of the run loop returns the same stale value no matter what the target did. A short Task.sleep on the main actor yields back to the run loop, which gives AppKit a turn to refresh; each sleep is one more chance to see the real state. The loop runs up to 80 times at 15 ms — a 1.2 second deadline. The original loop polled every 40 ms inside the same deadline; the review pass shortened the interval so that a hide that lands quickly is confirmed quickly, without making the deadline any longer.
When the Deadline Passes
If 1.2 seconds go by and the state still does not match, the adapter throws an ordinary error naming the app and whether the request had been accepted. It does not retry and it does not guess. The engine keeps the prepared receipt with that message, rolls back the confirmed changes of the same preset, and the Recovery tab shows the app as uncertain: "Inspect this app manually first."