🐞 Don't let no-op actions clobber the cached base frame (grow-from-zero) - #1117
🐞 Don't let no-op actions clobber the cached base frame (grow-from-zero)#1117bit-saver wants to merge 3 commits into
Conversation
`recomputeTargetFrame` cached the zero-size sentinel that no-frame actions (.noAction/.noSelection/.cycle/.minimize/.hide and window-focus actions) return. In a preview-only chord, `lastAppliedFrame` is nil, so a following grow/shrink/move reads that sentinel from `cachedTargetFrame.raw` as its base and resizes from a zero-size box at screen center. Skip caching for those actions, preserving the real base frame (matches pre-refactor behavior).
db93ef6 to
960f90f
Compare
|
Rebased onto the latest |
|
Thanks for this PR! I took a look through the logic, and the fix for the original The first is window focus actions. The new guard preserves the cached frame, but once the focus action is applied, The It may make sense to centralize this as something like Edit: also, you can ignore the swiftformat errors that are unrelated to this PR :) |
…hange - Add `WindowDirection.hasTargetFrame` and use it in both `WindowFrameResolver` and `ResizeContext`, so the two no-frame lists can't drift. It now also covers `.minimizeOthers` and space/screen-switching actions (previously fell through to a zero frame), fixing `minimizeOthers > grow` grow-from-zero. - Reset `cachedTargetFrame` to the new window's frame in `ResizeContext.setWindow(to:)` so `focus to another window > grow` resizes the newly-focused window, not the previous one.
|
Thanks for the thorough review — all three addressed:
Left the unrelated swiftformat errors alone per your note. Thanks! |
|
Thanks for addressing those! I took another look, and the previous issues are fixed, but I noticed one remaining behavior change caused by the new early return :) When I'm thinking that |
Now that `recomputeTargetFrame` keeps the cached frame for frameless actions (so grow/shrink/move can resize from it), `getTargetFrame()` returns the previous action's frame for a frameless action — which left the preview visible (e.g. `leftHalf > noAction`). Gate `PreviewViewModel.shouldBecomeVisible` on `WindowDirection.hasTargetFrame` so frameless actions hide the preview, restoring the pre-change behavior without losing the cache fix.
|
Good catch! Fixed — |
Summary
During a preview-only chord that starts with a no-op action (e.g. trigger + a "do nothing" action bound to a key, then an arrow to grow), the green preview collapses to a tiny zero-size box in the screen center, and the subsequent grow/shrink resizes from that box instead of the window's real frame.
Root cause
ResizeContextis reused across a chord.recomputeTargetFrameunconditionally caches the frame returned byWindowFrameResolver.getFrame. For no-frame actions (.noAction/.noSelection/.cycle/.minimize/.hideand window-focus actions),getFramereturns a zero-size centered sentinel — which then overwritescachedTargetFrame.raw. When the user next presses grow/shrink/move,WindowFrameResolver.calculateTargetFramereadscontext.lastAppliedFrame ?? context.cachedTargetFrame.rawas its base. In a preview-only chordlastAppliedFrameis stillnil(it's only set on an actual apply), so it falls through to the pollutedcachedTargetFrame.raw= the zero box, and grows from zero.Pre-refactor (1.4.2) never cached the sentinel: its no-op early-return sat before the
lastTargetFramestore, so the grow base stayed the real window frame. The refactor intorecomputeTargetFramelost that invariant.Fix
Skip caching in
recomputeTargetFramefor no-op / no-frame / window-focus actions (mirroring the guard already inWindowFrameResolver.getFrame), preserving the previously-cached real frame. Incremental grow during preview still works because real actions keep updatingcachedTargetFrame.raw.Testing
Built (Release) and verified:
trigger + do-nothing + Upnow grows from the window's real frame; repeated grow/shrink still steps correctly; normal single actions unaffected.