Skip to content

feat(runtime): preserve native column resizing - #145

Merged
qeude merged 19 commits into
mainfrom
feat/94-native-column-resizing
Oct 25, 2026
Merged

qeude merged 19 commits into
mainfrom
feat/94-native-column-resizing

Conversation

@qeude

@qeude qeude commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

Dragging a tiled window's side edge sets its column width, and the width stays. The overlay stretches the window and its neighbours with the pointer. Hidden windows wait at the side edge again. Closes #94.

 on(left mouse down on a tiled window)
+  record the press location (side-edge band: 3 pt inside, 8 pt outside)
 on(drag)
-  wait for a snapshot to show the resize, then carry the window
+  stretch the overlay on the first 2 pt along a side edge, or the first WindowServer resize
+  clamp the stretch to the window's min/max width, on the ribbon slot
+  pause desktop sync while stretching (no CGWindowList lock contention)
 on(release)
   read the released frame from the WindowServer
   learn width = released frame + gaps, ignoring the window's own bounds
-  drop the window's width bounds
+  drop only bounds the released frame contradicts, clear learned rounding
+  a dragged left edge stays where it was released (the ribbon before it scrolls)
+  a late frame matching a pending Defi write is not learned
 parkedFrame(hidden window)
-  bottom-right corner, then other corners and edges
+  nearest side edge at the window's own height, then the other side edge,
+  then corners and the bottom edge, first spot clear of other displays

Evidence

  • Before: while dragging an edge, 5 to 10 freezes of about 500 ms over 12 slow drags. Neighbours sometimes stood still. Windows from other workspaces crossed the screen on release. Hidden windows came in diagonally from the bottom-right corner.
    After: 0 freezes over 12 slow drags. Neighbours follow from the first event. Other workspaces stay parked on release (anotherWorkspacesParkedWindowStaysPutWhenTheRibbonMoves). Installed build: hidden windows at x=1511 and x=-375, y=37, with parkingRepairs=0.
  • python3 script/verify.py local passes on b7c582c (build, Swift tests, workflow tests).
  • New pure tests, among them:
    • Stacked window resized past its maximum to the column width drops the maximum: fails before b7c582c, passes after.
    • Mouse resize forgets the learned width rounding, A request just under the window's minimum is a clamp, not rounding.
    • ParkingTests: side-edge table, right-half window, displays on both sides fall back to the bottom edge.
  • Not covered: the two-display lane and the animations-off release path are tested only by pure tests. Desktop E2E did not run on the final head.

Merge Danger

Door: two-way

Revertible commits, with no stored data or config changes. The parking change also reverses #152's corner parking. AGENTS.md is updated to match.

Blast Radius: wide

Every hidden window moves to a new parking spot once, after the upgrade. A regression would show as a resize that snaps back, a stretch that lags or starts during a reorder drag, or a parked window showing on a side edge or on another display.

Note

Preserve native column resizing through gesture release, width learning, and parking changes

  • Lets the daemon detect a mouse release even when the native resize observation is missing, by tracking that the held gesture changed the window frame in Daemon.mouseGestureChangedWindowFrame and settling against the latest WindowServer frame.
  • Skips desktop sync while a live border gesture stretches the proxy, and caps the sync timer at min(display refresh, 120 Hz).
  • Reworks width learning in learnTiledWindowWidth to keep valid min/max bounds, drop only bounds the observed frame violates, clear stale width rounding, and adjust workspace scroll offset from the released window's left edge. mouseGestureWidthLearningFrame now prefers a release frame and ignores frames matching recent internal Defi writes.
  • Adds AXFrameCoordinator.frameMatchesPendingInternalWrite to identify frames that match an in-flight internal write, and changes acceptTiledWindowFrame to not count minimum-width clamps as width-rounding overshoot.
  • Moves parking to side-edge candidates at the window's current height, and lets cycleWidth step from off-preset pixel widths when a viewport width is supplied.
  • Behavioral Change: parkedFrame in Parking.swift returns side-edge positions instead of bottom/corner fallbacks; nearestPresetIndex in WorkspaceScrolling.swift is removed. Adds a manual verification guide at column-resizing.md.

Macroscope summarized b7c582c.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 10 files

Confidence score: 4/5

  • In Sources/DefiRuntime/ScrollReconciliation.swift, learning width on each resize sample clones runtime state, including monitor and window collections, adding avoidable work during a drag. Compute the unbounded learning inputs without cloning the full state.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/DefiRuntime/ScrollReconciliation.swift">

<violation number="1" location="Sources/DefiRuntime/ScrollReconciliation.swift:66">
P3: Width learning now clones and mutates the runtime state on every resize sample, forcing copy-on-write of the monitor and window collections during a drag. Compute the unbounded learning inputs without cloning the entire `RuntimeState` to avoid avoidable per-frame allocation and latency.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread Sources/DefiMacOS/MacOSPlatform+FramePerformance.swift Outdated
Comment thread Sources/DefiMacOS/MacOSPlatform+FramePerformance.swift Outdated
Comment thread .agents/skills/verify-defi/features/column-resizing.md Outdated
Comment thread Sources/DefiRuntime/ScrollReconciliation.swift
@macroscopeapp

macroscopeapp Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This PR adds native column resizing as a coordinated feature across the runtime, daemon synchronization, macOS event handling, WindowServer reconciliation, and overlay renderer, while also changing parking behavior. Its cross-layer state machine and production-path behavior changes are substantial enough to require human review.

You can add or adjust custom eligibility rules. Learn more.

qeude added a commit that referenced this pull request Oct 19, 2026
Match a late post-release frame only against in-flight write targets, and
document the helper build step in the column-resizing journey.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread Sources/DefiMacOS/AXFrameCoordinator.swift Outdated
qeude added a commit that referenced this pull request Oct 19, 2026
Size-only in-flight writes keep the window at its completed origin, so
match late frames against that origin rather than the requested point.
qeude added a commit that referenced this pull request Oct 22, 2026
Match a late post-release frame only against in-flight write targets, and
document the helper build step in the column-resizing journey.
@qeude
qeude force-pushed the feat/94-native-column-resizing branch from ad55b38 to 7d0d973 Compare October 22, 2026 23:11
qeude added a commit that referenced this pull request Oct 22, 2026
Size-only in-flight writes keep the window at its completed origin, so
match late frames against that origin rather than the requested point.
@qeude
qeude changed the base branch from main to feat/window-proxy-ribbon October 22, 2026 23:11
qeude added a commit that referenced this pull request Oct 22, 2026
Match a late post-release frame only against in-flight write targets, and
document the helper build step in the column-resizing journey.
qeude added a commit that referenced this pull request Oct 22, 2026
Size-only in-flight writes keep the window at its completed origin, so
match late frames against that origin rather than the requested point.
@qeude
qeude force-pushed the feat/94-native-column-resizing branch from 7d0d973 to 05bea31 Compare October 22, 2026 23:13
Base automatically changed from feat/window-proxy-ribbon to main October 23, 2026 12:02
Comment thread Sources/DefiDaemon/DaemonDesktopSynchronization.swift Outdated
Comment thread Sources/DefiDaemon/DaemonDesktopSynchronization.swift Outdated
Comment thread Sources/DefiDaemon/DaemonDesktopSynchronization.swift Outdated
qeude added 6 commits October 23, 2026 14:19
Restart the width-mismatch stability clock for the gesture window so a
user-held width cannot age into an app constraint after release, and
ignore post-release frames that match a recent Defi write.
A completed write equals the pre-gesture frame, so dragging a window back
to its start width must still count as the user's width.
Match a late post-release frame only against in-flight write targets, and
document the helper build step in the column-resizing journey.
Size-only in-flight writes keep the window at its completed origin, so
match late frames against that origin rather than the requested point.
@qeude
qeude force-pushed the feat/94-native-column-resizing branch from 05bea31 to 7788a73 Compare October 23, 2026 12:24
Apply an overlay drop on release only when the window moved, so a preview
left by an interrupted drag can never turn an edge resize into a drop.
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 23, 2026
qeude added 2 commits October 24, 2026 13:44
Adapt the PR to #152 and #153: keep the release-time translation guard on
the #153 drop preview, keep both verify-defi journeys, and drop the
removed animatesSize and isReentering arguments from the motion write
test helper.
#152 made minimumTiledWidth and maximumTiledWidth the window's own width,
with the layout adding the column's gaps. Native resize learning still
compared them with column widths, so it dropped a minimum the window
honored and ignored sibling bounds by one gap.
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 24, 2026 11:44

Dismissing prior approval to re-evaluate b3d056c

Comment thread Sources/DefiRuntime/ScrollReconciliation.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Confidence score: 3/5

  • In ScrollReconciliation.swift, bypassing effectiveMinimumTiledWidth can make a near-request native minimum look like the slot width after gaps, causing a frame at the normal layout target to be mislearned as a user resize. Use the effective minimum during reconciliation.

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.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/DefiRuntime/ScrollReconciliation.swift">

<violation number="1" location="Sources/DefiRuntime/ScrollReconciliation.swift:187">
P2: This bypasses `effectiveMinimumTiledWidth`, so a near-request native minimum is treated as a binding slot width after adding gaps. A frame already at the normal layout target can be falsely learned as a resize and have its minimum cleared; apply the same rounding rule when deriving `currentSlotWidth`.

(Based on your team's feedback about preserving near-request tiled widths as rounding.) [614eb153-c380-4bea-a404-2db8b3e7a129]</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread Sources/DefiRuntime/ScrollReconciliation.swift
qeude added 2 commits October 24, 2026 14:14
Learning the width on every sync tick laid the workspace out mid-drag. Each
layout restarted the overlay motion: neighbors sprang to the latest layout
instead of following the edge, and a window entering the view slid in from
its parked corner. While the overlay holds the gesture, the width is now
learned once, from the released frame.

Cycle width also steps from a width off the presets, such as a native
resize, to the next wider or narrower preset instead of restarting.
The overlay scrolls the ribbon with a dragged left edge, but the learned
width grew the window to the right on release, so the columns before it
sprang back. The ribbon now scrolls by the edge's travel.
qeude added 2 commits October 24, 2026 14:27
Desktop reads wait while the overlay draws, so the snapshot taken at
release could hold a frame from before the drag and the width was lost.
The button state can read up one snapshot before the gesture's release
event lands. That snapshot finished the gesture as inactive, so the
release lost its start frame and the overlay-held width was never learned.
Comment thread Sources/DefiRuntime/ScrollReconciliation.swift
Comment thread Sources/DefiDaemon/DaemonDesktopSynchronization.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 12 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

Comment thread Sources/DefiDaemon/DaemonDesktopSynchronization.swift
Comment thread Sources/DefiRuntime/ScrollReconciliation.swift
Comment thread Sources/DefiDaemon/DaemonDesktopSynchronization.swift
…idths

A parked window joined the stretch at its corner's height, so it slid in along
the bottom. The stretch now clamps to the window's own width bounds and masks
the start picture, which spilled into the gap once narrowed. A request just
under a window's minimum is no longer learned as rounding, and a user resize
clears stale rounding: both centred the window and widened its gaps.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 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

Comment thread Sources/DefiMacOS/MacOSPlatform.swift Outdated
Comment thread Sources/DefiRuntime/ScrollReconciliation.swift
The stretch waited for a snapshot to see the resize, so on a fast drag the
native edge moved while its neighbours stood still. A drag event now starts it
as soon as the WindowServer shows the pressed tiled window resizing. The
stretch measures shifts from the window's ribbon place instead of a native
frame already resized, takes the held edge from the pointer, and keeps a
window still travelling to or from its parking corner at its ribbon place.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

You’re at about 97% 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

Comment thread Sources/DefiMacOS/RibbonProxyRenderer.swift Outdated
Comment thread Sources/DefiMacOS/MacOSPlatform.swift Outdated
A terminal resizes in whole cells, so waiting for its native edge to move held
the neighbours still for over 100 ms. A press on a tiled window's side edge
now starts the stretch at the first horizontal move of 2 points.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 1 file (changes from recent commits).

You’re at about 97% 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

Comment thread Sources/DefiMacOS/MacOSPlatform.swift
Comment thread Sources/DefiMacOS/MacOSPlatform.swift Outdated
qeude added 3 commits October 25, 2026 00:49
…lease

During a stretch the daemon kept snapshotting the desktop. Its window list
waited on the WindowServer, busy with the live resize, while holding the
connection the overlay commits through, so the stretch froze for half a
second. The daemon now skips snapshots until release, which learns the width.

A release re-parks other workspaces' windows. Without ribbon history they
started shifted by the ribbon's motion and crossed the screen into their
corner; they now stay where they are.
Hidden windows waited in the bottom-right corner since #152, chosen to
leave other displays clear without the staircase. The staircase came back,
and any native motion the overlay misses now crossed the screen
diagonally. Park at the side edge nearest the window, at its own height,
as before #152 and as Rift, OmniWM and paneru do, so a missed motion is a
short horizontal slide. Corners and the bottom edge remain the fallback
when both side edges touch another display.
- Drop a contradicted width bound even when the column already has the
  released width, so a stacked window resized past its maximum keeps it.
- Advance a settling release instead of restarting it on every button-up
  snapshot, so the settlement times out with animations off.
- Release the gesture scroll anchor once a released resize is learned, so
  it no longer undoes the left-edge scroll shift.
- Read the side-edge press at the event's location, not when the queue
  runs.
- Keep the stretch width range under a native maximum below 240 pt.
@qeude
qeude merged commit 97938c4 into main Oct 25, 2026
4 checks passed
@qeude
qeude deleted the feat/94-native-column-resizing branch October 25, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1] Preserve native column resizing

1 participant