Skip to content

feat: keep automatic stacks within application size constraints - #148

Open
qeude wants to merge 16 commits into
mainfrom
feat/93-stack-size-constraints
Open

qeude wants to merge 16 commits into
mainfrom
feat/93-stack-size-constraints

Conversation

@qeude

@qeude qeude commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Why

Stacked windows got equal slots even when an app could not shrink that far. The app refused the height, Defi retried it on every desktop sync, and the window overflowed its slot. With the proxy overlay from #150 the refusal is also visible: the overlay draws the picture at the asked height, then fades to a native window of another height.

Closes #93.

What changed

This PR stacks on #150 (feat/window-proxy-ribbon).

  • Window keeps a verified minimumTiledHeight from the existing optional private constraint read. Without that read, stacks behave as before.
  • Sources/DefiCore/StackConstraints.swift splits a column by water-filling within each window's verified minimum and maximum height. Unconstrained stacks are split exactly as before.
  • join-window refuses a stack that cannot fit and leaves the workspace untouched. The CLI prints cannot join: stack needs 1650pt but only 1367pt is available.
  • When a stack stops fitting after a monitor, reserved-area or constraint change, the longest top run stays and the rest moves into new columns to its right. Columns never merge back on their own.
  • A single window taller than the viewport is targeted at its minimum, top-aligned, and listed as oversized-stack-windows= in defi status.
  • New on top of feat(ribbon): animate windows through a proxy overlay #150: a mouse drag only offers a stack when that stack fits. Otherwise the drop reorders the column. The overlay's stack preview uses the same slot heights the layout will apply.

Scope

Stack heights, join-window, and the drag-to-stack target from #150. Overview drops into a stack are unchanged. If such a stack cannot fit, the separation rule splits it on the next sync.

Tradeoffs

  • Targets now match what the app accepts, so the overlay's per-window settle learning only sees small residual adjustments. It never has to absorb a refused height.
  • Allocation ignores per-request TiledSizeAcceptance. Changing a request from it would start a resize battle.
  • The window server reports a minimum no larger than the current frame. A window that opens below its own minimum is read with a smaller minimum until the next topology read. Separation then catches up.

Blast Radius

Pure layout and runtime code, plus one more field from a constraint read that already ran. No new Accessibility writes, no per-frame work, no overlay or border code. The drag change only adds a feasibility check and a slot-height computation per pointer update, both pure math over one column.

Verification

  • python3 script/verify.py local passes on this head. One earlier run failed FrameGeometryReadTests on a 3 s timing wait outside this diff. The suite passed alone and the full rerun passed.
  • On the previous feat(ribbon): animate windows through a proxy overlay #150 head (7f6d7608), python3 script/verify.py desktop <run> --wait --filter 'DesktopE2ETests/(testAppliedTargetConvergesWithRealWindowFrame|testManagedResizeAnimationConvergesWithAdaptiveSizeWrites|testReenteringWindowJoinsFirstAnimatedRibbonSample|testHorizontalAnimationFrameWritesPositionWithoutSize)' ran 4 tests, 4 passed, 0 skipped. Accessibility was available, and the session restore passed. One earlier run passed the same 4 tests but its restore hit a LaunchServices open error -600. A manual restore under the desktop lock then succeeded.
  • On that same head, python3 script/desktop_lock.py --wait python3 .agents/skills/verify-defi/stacks_check.py on the installed build: all six scenarios passed, with one daemon left afterwards.
  • New unit tests cover a drag into a stack that cannot fit and a preview that keeps the dragged window's minimum.
  • Not done: Computer Use with a real drag over constrained apps.
  • The latest feat(ribbon): animate windows through a proxy overlay #150 commits only change AGENTS.md and the duration_ms = 0 workspace switch. They do not touch stacks or drag, so the desktop checks were not repeated on this head.

Note

Add automatic stack separation and window height constraints to Defi layout

  • Windows now carry a discovered minimumTiledHeight (Window.swift, WindowBorderPrivateProviders.swift), and stack slots are allocated with a water-filling algorithm that honors minimum and maximum heights per window (StackConstraints.swift).
  • Stacks that cannot fit are handled at three points: joinFocusedWindow rejects infeasible joins with a StackRejection error before mutating state, mouse drops hide stack targets that cannot fit, and synchronizeScrollOffsets automatically separates infeasible columns into right-adjacent columns while preserving window order, focus, and scroll anchors (ScrollReconciliation.swift).
  • Separated columns are never re-merged, even after the viewport grows. Single windows whose minimum exceeds the viewport remain unchanged and are reported as oversized-stack-windows in defi status.
  • Adds an executable end-to-end verifier (stacks_check.py) with a ClampFixture DEFI_FIXTURE_MIN mode, plus unit tests for allocation, rejection, separation, and mouse previews.
  • Risk: existing workspaces with height-constrained windows may separate on first reconciliation after this change; check separateInfeasibleStacks in StackConstraints.swift and the computeLayout / acceptedTiledSizes changes in Layout.swift, which now use constrained slot geometry instead of equal-height slots.

Macroscope summarized 488a223.

@qeude
qeude force-pushed the feat/93-stack-size-constraints branch from 00d208f to 0dc4422 Compare October 19, 2026 21:52
Comment thread Sources/DefiCore/StackConstraints.swift Outdated
@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 introduces substantial production behavior: constraint-aware stack sizing, rejected joins, drag-target changes, and automatic splitting of existing stacks when a viewport or app constraint changes. Because it adds a new user-facing workflow and changes multiple existing runtime paths, the scope warrants human review.

No code changes detected at 488a223. Prior analysis still applies.

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

qeude added a commit that referenced this pull request Oct 19, 2026
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.

@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.

Review completed against the latest diff

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

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

Comment thread .agents/skills/verify-defi/stacks_check.py Outdated
Comment thread .agents/skills/verify-defi/stacks_check.py
Comment thread Sources/DefiCore/StackConstraints.swift Outdated
Comment thread .agents/skills/verify-defi/stacks_check.py
Comment thread .agents/skills/verify-defi/features/stacks.md Outdated
Comment thread Sources/DefiMacOS/WindowBorderPrivateProviders.swift Outdated
Comment thread .agents/skills/verify-defi/stacks_check.py Outdated
Comment thread Sources/DefiDaemon/DaemonStatusLifecycle.swift Outdated
Comment thread .agents/skills/verify-defi/stacks_check.py Outdated

@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.

2 issues found across 18 files

Confidence score: 3/5

  • In stacks_check.py, the unscoped calls change every monitor, but the journey selects only the first; cleanup can also overwrite every monitor’s bottom edge with one restore value. Scope the changes to the test monitor and restore its original bottom edge.
  • In ScrollReconciliation.swift, splitting stacks on every monitor can leave monitors with no submitted layout in the old arrangement. Propagate the changed monitor IDs and include them in the layout.
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:57">
P2: This splits stacks on every monitor, but callers can submit layout for only a subset, leaving other monitors' native frames in the old stack arrangement. Propagate the changed monitor IDs and include them in the layout submission.</violation>
</file>

<file name=".agents/skills/verify-defi/stacks_check.py">

<violation number="1" location=".agents/skills/verify-defi/stacks_check.py:186">
P1: These unscoped calls change every monitor, although the journey selects only the first; cleanup then replaces every bottom edge with one `--restore-bottom` value. Scope the changes to the test monitor and restore its saved edge.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 5 unresolved issues already reported by Cubic.

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

Comment thread .agents/skills/verify-defi/stacks_check.py
Comment thread .agents/skills/verify-defi/stacks_check.py
Comment thread Sources/DefiRuntime/ScrollReconciliation.swift
Comment thread .agents/skills/verify-defi/stacks_check.py Outdated
Comment thread .agents/skills/verify-defi/stacks_check.py Outdated
Comment thread Sources/DefiRuntime/CommandReducer.swift
Comment thread CONFIGURATION.md Outdated
Comment thread CONFIGURATION.md Outdated
Comment thread Sources/DefiCore/Layout.swift Outdated
qeude added a commit that referenced this pull request Oct 19, 2026
- 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

@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 6 files (changes from recent commits).

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

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

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/DefiCore/StackConstraints.swift">

<violation number="1" location="Sources/DefiCore/StackConstraints.swift:15">
P3: `StackConstraints.swift` now uses Foundation's `formatted(...)` but does not import Foundation. It compiles only because `DefiModel` imports Foundation and Swift leaks that import. Add `import Foundation` explicitly.</violation>
</file>

Comment thread Sources/DefiCore/StackConstraints.swift
qeude added a commit that referenced this pull request Oct 19, 2026
- 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
qeude added a commit that referenced this pull request Oct 19, 2026
Import Foundation explicitly where StackRejection formats heights.
Comment thread Sources/DefiCore/StackConstraints.swift
qeude added a commit that referenced this pull request Oct 19, 2026
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.

@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 3 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/DefiCore/Layout.swift
qeude added a commit that referenced this pull request Oct 22, 2026
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.
qeude added a commit that referenced this pull request Oct 22, 2026
- 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
qeude added a commit that referenced this pull request Oct 22, 2026
- 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
qeude added a commit that referenced this pull request Oct 22, 2026
Import Foundation explicitly where StackRejection formats heights.
qeude added a commit that referenced this pull request Oct 22, 2026
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.
@qeude
qeude force-pushed the feat/93-stack-size-constraints branch from 881c670 to 3f09f75 Compare October 22, 2026 23:24
@qeude
qeude changed the base branch from main to feat/window-proxy-ribbon October 22, 2026 23:24
qeude added a commit that referenced this pull request Oct 22, 2026
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.
qeude added a commit that referenced this pull request Oct 22, 2026
- 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
@qeude
qeude force-pushed the feat/93-stack-size-constraints branch from 3f09f75 to e7c5862 Compare October 22, 2026 23:34
qeude added a commit that referenced this pull request Oct 22, 2026
- 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
qeude added a commit that referenced this pull request Oct 22, 2026
Import Foundation explicitly where StackRejection formats heights.
qeude added a commit that referenced this pull request Oct 22, 2026
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.
qeude added a commit that referenced this pull request Oct 22, 2026
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.
qeude added a commit that referenced this pull request Oct 22, 2026
- 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
@qeude
qeude force-pushed the feat/93-stack-size-constraints branch from e7c5862 to b89d0d1 Compare October 22, 2026 23:37
qeude added a commit that referenced this pull request Oct 22, 2026
- 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
qeude added a commit that referenced this pull request Oct 22, 2026
Import Foundation explicitly where StackRejection formats heights.
qeude added a commit that referenced this pull request Oct 22, 2026
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.
Base automatically changed from feat/window-proxy-ribbon to main October 23, 2026 12:02
qeude added 14 commits October 23, 2026 14:19
Balanced water-filling over per-window minimum and maximum heights, a pure
stack fit decision, and a deterministic separation of impossible stacks.
…eight

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.
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.
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.
- 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
- 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
Import Foundation explicitly where StackRejection formats heights.
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.
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.
@qeude
qeude force-pushed the feat/93-stack-size-constraints branch from b89d0d1 to 4fe0976 Compare October 23, 2026 12:24
qeude added 2 commits October 23, 2026 20:38
…nstraints

# Conflicts:
#	.agents/skills/verify-defi/features/README.md
#	Sources/DefiCore/Layout.swift
#	Sources/DefiDaemon/DaemonStatusLifecycle.swift

This branch has not been deployed

No deployments
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] Keep automatic stacks within application size constraints

1 participant