Repository navigation
feat(ribbon): preview drops on another display like on a single one - #153
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 8 files
You’re at about 94% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
f62a151 to
e7df7ee
Compare
ApprovabilityVerdict: Needs Human Approval Macroscope's review found this PR requires human approval — This PR introduces substantial cross-display tiled-window drop behavior, including new runtime state transfer and coordinated previews across multiple macOS overlays. The cross-component interaction and new user-facing workflow merit human review despite the accompanying tests. No code changes detected at You can add or adjust custom eligibility rules. Learn more. |
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
You’re at about 96% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
A drag whose pointer leaves its display now targets the gap under the pointer in the other display's active workspace. Release inserts the window there instead of where the native cross-display move puts it. The drop revalidates the destination and changes nothing when that workspace is no longer active or the gap is out of range. A 24 pt hysteresis keeps a pointer resting on a target boundary from flipping the target, so the overlay preview does not restart its springs. The daemon traces each preview change for the drag harness.
A drag over another display now puts that display's ribbon on its overlay too: its windows make room for the drop, the landing zone shows there, and a copy of the carried window follows the pointer above the native window. The source display closes the gap at the same time. Drop preview frames now cover every display's active workspace, and the landing zone shows only on the display the drop lands on. Coming back to the source display or releasing elsewhere lands both overlays as before.
- Never hold a target with the hysteresis when the pointer has none, so the window's own stack and off-display pointers keep the native path. - Host a target display only while the source carries, apply a preview computed before the carry once it starts, and clear it on release. - Retry a host that lacks the carried window's metadata at most every 100 ms instead of on every drag event. - drag_check reads only the current drag's preview events; the doc names the real switches for the overlay.
The hysteresis probed horizontal offsets only, so a pointer resting on the edge between stacked displays could still flip the target.
c3c4727 to
3bc8159
Compare
The overlay preview comes from mouseDropPreviewFrames since #153, so MouseStackTarget no longer carries a preview rectangle. A workspace moved to a fallback display after display loss keeps its stacks, so it returns unchanged when its display comes back.
* feat(model): keep verified minimum window height from native metadata * feat(core): allocate stack heights within verified size constraints Balanced water-filling over per-window minimum and maximum heights, a pure stack fit decision, and a deterministic separation of impossible stacks. * feat(runtime): reject impossible joins and separate stacks that stop fitting * feat(status): report oversized single-window stacks and document stack constraints * docs(verify-defi): add stacks journey and check script * fix(core): target oversized stack windows at their verified minimum height A target below a window's minimum is refused by the app and was re-sent on every desktop sync. Raising the target to the minimum, top-aligned, matches what the app accepts, mirroring how widths clamp to their minimum. * docs(verify-defi): keep stacks journey on its workspace and check oversized retries * test(fixture): open clamp fixture windows at their minimum size The window server reports a minimum no larger than the current frame, so a fixture window opened below its minimum hid it from the first constraint read. Also make the stacks journey wait for focus and adoption, judge oversized retries by applied writes, and fail if another install replaces the daemon mid-run. * fix: address PR review feedback (#148) Count each unconstrained window's gaps and 1pt drawable height in a stack's lower bound, so stackFit no longer accepts a stack that squeezes a sibling below the frame applyGaps emits. * fix: address PR review feedback (#148) - format stack rejection heights as Double so an extreme minimum cannot trap - make WindowSizeConstraints.minimumHeight a let so callers cannot drop it - exclude native fullscreen windows from oversized-stack-windows - stacks journey: exact window ID matching, every completion kind, require the stack-constraint error, and fail unless exactly one daemon remains * fix: address PR review feedback (#148) - compute accepted-size slot offsets in one pass - describe viewport-based limits and capped-maximum centring in CONFIGURATION.md - stacks journey: require one monitor, validate --restore-bottom, and close a fixture whose setup fails * fix: address PR review feedback (#148) Import Foundation explicitly where StackRejection formats heights. * fix: address PR review feedback (#148) Enforce a window's verified maximum height on its final gapped frame, since applyGaps shrinks gaps wider than half a frame after the slot-level cap. * fix(runtime): keep drag stacking within verified stack heights A drag over a column only offers a stack when the stack fits the windows' verified minimum heights, so a drop never stacks and then splits. The overlay preview uses the same slot heights the layout will apply. * fix(runtime): keep migrated stacks and drop the unused stack preview The overlay preview comes from mouseDropPreviewFrames since #153, so MouseStackTarget no longer carries a preview rectangle. A workspace moved to a fallback display after display loss keeps its stacks, so it returns unchanged when its display comes back.
Closes #95. Supersedes #147.
Builds on #152 (merged).
Why
On a single display, a drag shows the drop before release: the ribbon makes room and a landing zone marks the slot. Across displays, there was no preview. The overlay of the target display stayed down, the landing zone stayed on the source display, and the carried picture was cut at the source display's edge. On release, the window landed at the end of the other ribbon.
What changed
MouseDropTarget.display(MonitorID, workspaceID:gap:)targets the gap under the pointer in another display's active workspace.applyMouseDroprevalidates it and inserts the window there. A 24 pt hysteresis keeps a pointer resting on a boundary from flipping the target.mouseDropPreviewFramesmoves toDefiRuntimeand lays out every display's active workspace in the previewed state. The source display closes the gap, and the target display opens the slot.RibbonProxyPresenter.hostputs the target display's ribbon on its overlay (RibbonProxyDrag.hosting). Its windows make room, the landing zone shows there, and a copy of the carried picture follows the pointer above the native window hidden below. The landing zone shows only on the display the drop lands on.RibbonProxyPointerTrackmoves the carried copy on each hosting display with the same grab offset, in the same display-refresh tick.Scope
animation.window_proxies = true, the default).Blast radius
Sources/DefiMacOS/RibbonProxyRenderer.swift: newhostingdrag state,host,showSlot, and pointer-track guests. Carry, stretch and single-display preview behave as before.Sources/DefiMacOS/MacOSPlatform.swift:hostWindowProxyPreview. The carry window list is factored intowindowProxyCarryWindows.Sources/DefiRuntime/MouseReordering.swift,Sources/DefiDaemon/DaemonDesktopSynchronization.swift: target and preview frames.Verification
python3 script/verify.py localon the final head: passed. NewMouseReorderingTestscase: the preview closes the gap on the source and opens the slot on the target.verify.py desktop, fullDesktopE2ETests, two displays, Accessibility available: one failure,testSettingsSearchRevealsOffscreenOptionAgain(native focus of a fixture). It passed when rerun alone. Session restoration passed.defi-daemonremained after each run.Note
Show ribbon overlay and window-proxy previews on every display
Reworks the ribbon overlay and window-proxy system to run per display instead of once globally, so dragging and previewing a window onto another display behaves like on a single display.
RibbonProxyRendererandRibbonProxyPresenternow keep one overlay panel and presenter per display, with per-display motion state, background captures, and pointer tracking that moves hosted window copies on the destination display (RibbonProxyRenderer.swift).MacOSPlatformwindow-proxy carry resolves the display containing the dragged window, hosts a preview copy on the destination display, and retries when the display or presenter is unavailable (MacOSPlatform.swift).MouseDropTargetgains a display case, anddropTiledWindowmoves a tiled window into another display's active workspace gap with 24-point boundary hysteresis (MouseReordering.swift).WindowBorderManagerhides border sets per display rather than globally, so one display's overlay can hide its windows without suppressing borders on other displays (WindowBorderManager.swift).enabledflag, and quarantine uses fixed 0.18/0.8-second durations (FrameCommit.swift, ConfigModels.swift).AnimationConfigno longer decodesduration_msorwindow_proxies; existing configs carrying those keys are silently ignored.positionsCanAnimateTogether, the pending-animation status properties onMacOSPlatform, and the public spring/animation utilities inAnimation.swiftare removed, so out-of-tree callers of those APIs break.Macroscope summarized c3c4727.