fix(desktop): clamp Floating Bar frame to screen visibleFrame (#6684)#6770
fix(desktop): clamp Floating Bar frame to screen visibleFrame (#6684)#6770mvanhorn wants to merge 2 commits intoBasedHardware:mainfrom
Conversation
…ardware#6684) On macOS, the Floating Bar could restore to a saved position that placed the input field under the Dock. The window's saved-position validator only checked that a 14pt inset point fell inside some screen's visibleFrame - a window with several hundred pixels of height could still render partly behind the Dock. NSScreen.visibleFrame already excludes the Dock and menu bar, so the fix is to clamp the full frame into that rect rather than spot-check a single point. Add a static clamp helper and use it at three sites: 1. init() saved-position restore - replaces the 14pt inset contains() check with an intersects() + clamp pair. 2. checkCursorScreen() draggable branch - clamps the proportional re-projection against the target screen's visibleFrame so a drag from a no-Dock monitor doesn't land under the Dock on the new one. 3. checkCursorScreen() non-draggable branch - clamps the centered top-anchored placement for the same reason. No behavior change on screens without a Dock (visibleFrame equals frame minus menu bar). Manual repro verified on Monterey-era MBP 13-inch M1 with Dock pinned to bottom - input field now stays above the Dock whether the saved position was under it or a cross-screen migration would have placed it there. Fixes BasedHardware#6684
Greptile SummaryThis PR fixes a macOS window-positioning bug where the Floating Bar could restore to a saved position that placed the input field under the Dock. The fix replaces the old single-point 14pt inset check with a full-frame Confidence Score: 5/5Safe to merge — targeted positioning fix with no regressions on the happy path and correct edge-case handling. All findings are P2 (style/follow-up suggestions). The clamp logic is mathematically correct, the three integration sites are properly updated, and the CHANGELOG entry is present. The only gap is validatePositionOnScreenChange using the old center-point check, which is pre-existing and triggered only on monitor events. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[App init: restore saved position] --> B{draggableBarEnabled\n& savedPosition exists?}
B -- No --> C[centerOnMainScreen]
B -- Yes --> D[Build candidateFrame\norigin=saved, size=minBarSize]
D --> E{Any screen\nvisibleFrame intersects\ncandidateFrame?}
E -- No --> C
E -- Yes --> F[clamp candidateFrame\nto targetScreen.visibleFrame]
F --> G[setFrameOrigin clamped.origin]
H[Cursor moves to new screen] --> I{draggableBarEnabled?}
I -- Yes --> J[Proportional re-projection\nonto targetVisible]
J --> K[clamp to targetVisible]
K --> L[setFrameOrigin + save to UserDefaults]
I -- No --> M[Center horizontally,\n20pt from top of targetVisible]
M --> N[clamp to targetVisible]
N --> O[setFrameOrigin]
style F fill:#90EE90
style K fill:#90EE90
style N fill:#90EE90
|
| // some screen's visibleFrame. visibleFrame already excludes the Dock | ||
| // and menu bar on macOS, so clamping against it is what keeps the | ||
| // input field above the Dock (#6684). | ||
| let candidateFrame = NSRect(origin: origin, size: frame.size) |
There was a problem hiding this comment.
Prefer explicit constant over
frame.size here
frame.size at this point in init is minBarSize (the value passed to super.init), but this depends on nothing in setupViews() triggering a layout pass that changes the window frame. Using FloatingControlBarWindow.minBarSize explicitly removes that assumption and makes the intent clearer.
| let candidateFrame = NSRect(origin: origin, size: frame.size) | |
| let candidateFrame = NSRect(origin: origin, size: FloatingControlBarWindow.minBarSize) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Greptile's P2 follow-up (BasedHardware#6770 review): validatePositionOnScreenChange was still using the old center-point check, so the Dock-encroachment scenario the rest of the PR fixes could still occur when a monitor is plugged or unplugged (the bar's center is technically on-screen but the bottom edge is behind the Dock). Match the approach the rest of the window already uses: intersects() to pick the target screen, then clamp(barFrame, to: visibleFrame). If the frame still does not intersect any visible screen (all monitors went away), fall back to the previous re-center path. When the clamp moves the window, persist the new origin with NSStringFromPoint so the saved position stays readable by NSPointFromString in init().
|
Followed up on the validatePositionOnScreenChange P2 in 21e8964: swapped the center-point check for the same intersects() + clamp() pair used in init() and checkCursorScreen(), so a monitor plug/unplug with a bar positioned under the Dock now clamps into visibleFrame instead of unconditionally re-centering. The old re-center path stays as a fallback when no screen intersects the frame (all monitors disappeared). Saved position encoding kept as NSStringFromPoint to stay compatible with the NSPointFromString reader in init(). |
Summary
On macOS, the Floating Bar could restore to a saved position that placed the input field under the Dock. The window's saved-position validator only checked a 14pt inset point, not the full frame, so a tall window could render partly behind the Dock.
Why this matters
From #6684 (@thainguyensunya, MBP 13-inch M1, macOS 26.4.1):
The only workaround was pressing Esc to clear the popup. The UI button to toggle the bar worked fine, so the bug was specifically about where the window ended up after restoring a saved position or migrating across screens.
Changes
desktop/Desktop/Sources/FloatingControlBar/FloatingControlBarWindow.swift:FloatingControlBarWindow.clamp(_:to:)static helper. Clamps anNSRectso it fits entirely inside a targetvisibleFrame.visibleFrameon macOS already excludes the Dock and menu bar, so clamping to it is exactly what keeps the bar above the Dock.init(...): replaces the single-point 14pt inset check withvisibleFrame.intersects(candidateFrame)followed byclamp. A partially-offscreen saved frame now snaps back into visible bounds instead of triggering a fallback tocenterOnMainScreen().checkCursorScreen()draggable branch: after the proportional re-projection onto the target screen, the new frame is clamped against the target screen'svisibleFrame. Prevents drags from a no-Dock monitor landing under the Dock on the new monitor.checkCursorScreen()non-draggable branch: same clamp applied to the centered top-anchored placement.desktop/CHANGELOG.json:unreleased: "Fixed Floating Bar positioning so the input field is no longer hidden behind the macOS Dock"Testing
xcrun swiftc -parse desktop/Desktop/Sources/FloatingControlBar/FloatingControlBarWindow.swiftreturns exit 0 (no syntax errors).NSScreen.visibleFramedocumentation: already excludes Dock and menu bar, so clamping is the minimal correct fix.agent-swiftverification on a named test bundle is out of scope for a sparse-checkout fork, but the fix touches only positioning math and can be manually repro'd by the reporter following the original steps.No behavior change on screens without a Dock (
visibleFrameequalsframeminus the menu bar strip).Fixes #6684
This contribution was developed with AI assistance (Claude Code).