Skip to content

Fix window movability regression - #4983

Open
lederniermagicien wants to merge 4 commits into
manaflow-ai:mainfrom
lederniermagicien:fix-window-movability-regression
Open

lederniermagicien wants to merge 4 commits into
manaflow-ai:mainfrom
lederniermagicien:fix-window-movability-regression

Conversation

@lederniermagicien

@lederniermagicien lederniermagicien commented May 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Restore standard-mode main window isMovable so macOS tiling and third-party window managers can move cmux windows again.
  • Keep isMovableByWindowBackground disabled so app content, titlebar controls, folder proxy icon drags, and Bonsplit tab gestures do not become implicit background window drags.
  • Keep minimal mode native movability disabled; explicit cmux chrome drag handles still opt into performDrag when they intentionally move the window.
  • Preserve active window-move suppression while a protected drag sequence is in progress, and finish folder-drag suppression only when the active suppression reason matches.
  • Does not update vendor/bonsplit; this PR only addresses the cmux-side regression.

Testing

  • CMUX_NUCLEO_FFI_REQUIRE_CARGO=0 CMUX_SKIP_ZIG_BUILD=1 ./scripts/test-unit.sh test -only-testing:cmuxTests/TitlebarLeadingInsetPassthroughViewTests -only-testing:cmuxTests/FolderWindowMoveSuppressionTests -only-testing:cmuxTests/WindowMoveSuppressionHitPathTests
  • CMUX_SKIP_ZIG_BUILD=1 CMUX_NUCLEO_FFI_REQUIRE_CARGO=0 ./scripts/reload.sh --tag window-movable-regression
  • Manually verified the built tagged Debug app path was produced for QA.
  • Docs/changelog not updated: this is a narrow window-drag regression fix with no docs-facing behavior change.

Demo Video

For UI or behavior changes, include a short demo video (GitHub upload, Loom, or other direct link).

  • Video URL or attachment: Not included per request.

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed (N/A)
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Summary by cubic

Restores OS-level movability for the main window in standard mode while keeping background drags off; minimal mode stays immovable and uses explicit chrome drags. Preserves suppression state across mode changes and aligns suppression helpers to the main thread for safe UI updates.

  • Bug Fixes

    • Set isMovable by presentation mode: enabled in standard, disabled in minimal; keep isMovableByWindowBackground = false.
    • Do not re-enable movability during an active suppression; update the restore baseline on mode changes and keep the window immovable until suppression ends.
    • Finish folder-drag suppression on mouseUp/session end only when the reason matches via finishWindowMoveSuppressionSequence(...).
    • Re-apply window drag config when decorations apply to the main window and on mode changes, without breaking active suppressions.
    • Marked suppression and routing helpers @MainActor; used lightweight locks where needed to keep AppKit event paths synchronous and avoid cross-actor issues.
    • Expanded tests for standard/minimal baselines, no re-enable during suppression, mode-switch restoration, and folder-drag matching finish.
  • New Features

    • Added localized strings for Bluetooth and camera permission prompts in InfoPlist.xcstrings (zh-Hans/zh-Hant/ko/de/es/fr/it/da/pl/ru/bs/ar/nb/pt-BR/th/tr).

Written for commit 2ebd196. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Main-window drag behavior now respects presentation mode and active suppression sequences; background dragging stays disabled and prior movability is preserved/restored correctly.
  • Tests

    • Added extensive tests covering mode changes, suppression lifecycles, and folder-drag suppression behavior.
  • Localization

    • Added translations for permission usage strings across many additional locales.
  • Refactor

    • Improved main-thread safety and drag-handling coordination.

Review Change Stack

@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

@lederniermagicien is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented May 29, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR makes main-window drag configuration presentation-mode and suppression-aware, adds a reason-matching finish API for window-move suppression, integrates the matching finish into folder-drag lifecycle, and adds tests and localized InfoPlist strings.

Changes

Window Move Suppression with Reason Matching

Layer / File(s) Summary
Window move suppression matching API
Sources/WindowDragHandleView.swift
Marks suppression/hit-testing helpers @MainActor, expands NSLock rationale, adds updateActiveWindowMoveSuppressionSequencePreviousMovableState(window:previousMovableState:), and adds finishWindowMoveSuppressionSequence(window:matching:) which returns nil unless the active reason matches and otherwise delegates to the existing finish logic.
Mode-aware drag configuration and presentation-mode hooks
Sources/App/CmuxMainWindow.swift, Sources/WindowDecorationsController.swift, Sources/ContentView.swift
configureCmuxMainWindowDragBehavior now accepts defaults: UserDefaults = .standard, always disables isMovableByWindowBackground, computes a baseline from WorkspacePresentationModeSettings.isMinimal(defaults:), applies baseline only when no active suppression (or records previous movable state if suppression active); WindowDecorationsController is @MainActor and calls configuration for main windows; ContentView comments updated.
Folder drag suppression lifecycle integration
Sources/DetachedFolderDragIcon.swift
Calls finishWindowMoveSuppressionSequence(window:matching:.folderDrag) at draggingSession(_:endedAt:operation:) and mouseUp(with:) so .folderDrag suppression only finishes when reasons match.
Window drag and suppression lifecycle tests
cmuxTests/WindowAndDragTests.swift
Added DEBUG tests covering configure behavior in standard/minimal modes, reconfiguration during active .bonsplitPaneTabDrag suppression, finishing suppression and baseline restoration (including mode changes during suppression), and matching/non-matching finish behavior for .folderDrag.
Localization expansions
Resources/InfoPlist.xcstrings
Expanded NSBluetoothAlwaysUsageDescription and NSCameraUsageDescription with additional localized stringUnit entries for many locales.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#4290: Both PRs touch window-move suppression and main-window drag behavior for minimal-mode pane interactions.

Suggested reviewers

  • jesstelford

Poem

🐇 I nudge the titlebar, but reasons say no,
A sequence keeps movability low.
Only matching finishes lift the spell,
Minimal mode keeps the drag-zone well.
Tests hum softly — the windows behave so.

🚥 Pre-merge checks | ✅ 17 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix window movability regression' directly matches the PR's main objective: restoring standard-mode main window isMovable while keeping background drags disabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed All new/modified Swift functions are properly annotated with @MainActor and called only from MainActor-isolated contexts. @unchecked Sendable use is documented. No actor isolation mistakes introduced.
Cmux Swift Blocking Runtime ✅ Passed No new blocking synchronization introduced. Changes add @MainActor annotations and non-blocking helper functions; pre-existing NSLock usage in WindowDragHandleView is intentional and documented.
Cmux No Hacky Sleeps ✅ Passed Rule applies only to TypeScript, JavaScript, shell, and non-Swift build scripts. All PR changes are Swift files; Swift timing covered by separate check.
Cmux Algorithmic Complexity ✅ Passed All loops operate on small bounded collections (NSApp.windows ~10, tracking records few). No nested scans or hot-path inefficiencies. New operations are O(1) or O(n) with n bounded.
Cmux Swift Concurrency ✅ Passed PR adds @MainActor annotations and new synchronous functions with proper actor isolation. No new DispatchQueue.global(), Combine patterns, completion handlers, or fire-and-forget Tasks introduced.
Cmux Swift @Concurrent ✅ Passed PR adds @MainActor to 13+ synchronous window-drag helpers (all performing UI-bound state changes). No async/await, no heavy operations, no invalid @concurrent usage detected.
Cmux Swift File And Package Boundaries ✅ Passed All changes to existing files; 69 lines added to WindowDragHandleView (under 250-line threshold); focused bug fix on window movability; AppKit code appropriately in app target.
Cmux Swift Logging ✅ Passed NSLog in Sources/ContentView.swift is guarded by #if DEBUG for feedback submission diagnostics, complying with allowed debug-only logging per swift-logging.md rules.
Cmux User-Facing Error Privacy ✅ Passed No user-facing errors or sensitive information exposed. Changes focus on window drag behavior, actor isolation, and permission strings with no vendor names, credentials, or internal details.
Cmux Full Internationalization ✅ Passed PR properly localizes all user-facing changes: InfoPlist.xcstrings keys expanded to 18 locales, web messages updated across all 20 locales, and no unlocalized strings added to production Swift code.
Cmux Swiftui State Layout ✅ Passed PR contains AppKit bridge views and window management code with no new SwiftUI state violations. DocumentationUpdates only; @MainActor annotations applied correctly for AppKit safety.
Cmux Architecture Rethink ✅ Passed PR uses @MainActor isolation on window logic, associated object storage for state, NSLocks only for required AppKit deinit paths, no polling/timing repairs, clear ownership and invariants documented.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only modifies drag behavior of existing CmuxMainWindow (main workspace window), which is exempted by the rule. No new standalone windows are created.
Description check ✅ Passed PR description covers all required template sections with clear explanations of changes, comprehensive testing details, and complete checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 29, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores OS-level window movability for the cmux main window in standard mode (setting isMovable = true) while keeping isMovableByWindowBackground = false and keeping minimal mode fully immovable. It also hardens the folder-drag suppression lifecycle with a new matching-reason overload of finishWindowMoveSuppressionSequence, adds updateActiveWindowMoveSuppressionSequencePreviousMovableState to handle mode changes mid-drag correctly, and backfills @MainActor on several free functions that touch AppKit.

  • Movability baseline: configureCmuxMainWindowDragBehavior now reads the presentation mode and sets isMovable accordingly; if a suppression sequence is active, it updates the restore baseline instead of re-enabling movability.
  • Folder-drag cleanup: DraggableFolderNSView now calls finishWindowMoveSuppressionSequence(window:matching:.folderDrag) from both mouseUp and draggingSession(_:endedAt:operation:), preventing it from accidentally clearing another source's active suppression.
  • Localization: NSBluetoothAlwaysUsageDescription and NSCameraUsageDescription in InfoPlist.xcstrings now include 16 additional locale translations that were already present in the rest of the catalog.

Confidence Score: 5/5

Safe to merge; the movability baseline, suppression lifecycle, mode-change handling, and actor annotations are all correct and well-tested.

The core logic — setting isMovable per presentation mode, updating the suppression restore baseline on mode changes mid-drag, and finishing folder-drag suppression with a reason-matched overload — is implemented correctly and covered by new unit tests. @MainActor is applied consistently to all new and changed functions that touch AppKit. The only gap is two pre-existing shouldSuppressWindowMoveForFolderDrag overloads that lack the annotation added to their sibling in this PR, but they are only ever called from @MainActor contexts so there is no current unsafe path.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/CmuxMainWindow.swift Refactored configureCmuxMainWindowDragBehavior to set isMovable based on presentation mode with correct suppression-aware baseline update logic.
Sources/WindowDragHandleView.swift Adds updateActiveWindowMoveSuppressionSequencePreviousMovableState, a reason-matching finishWindowMoveSuppressionSequence overload, doc-comments, and @MainActor annotations on suppression helpers and windowDragHandleShouldCaptureHit.
Sources/App/ShortcutRoutingSupport.swift Adds @MainActor to four free functions that touch AppKit; shouldSuppressWindowMoveForFolderDrag(hitView:) and shouldSuppressWindowMoveForFolderDrag(window:event:) are not annotated while their sibling is, leaving a minor actor-isolation annotation gap.
Sources/DetachedFolderDragIcon.swift Adds finishWindowMoveSuppressionSequence(window:matching:.folderDrag) to both mouseUp and draggingSession(_:endedAt:operation:); the matching overload makes double-calls safely idempotent.
Sources/WindowDecorationsController.swift Class annotated @MainActor; apply(to:) now calls configureCmuxMainWindowDragBehavior for all main windows, ensuring mode-change notifications propagate drag config correctly in both standard and minimal mode.
Resources/InfoPlist.xcstrings Adds 16 locale translations for NSBluetoothAlwaysUsageDescription and NSCameraUsageDescription, matching the locale set already present in the rest of the catalog.
cmuxTests/WindowAndDragTests.swift Expands test coverage with 5 new cases: standard/minimal baselines, no re-enable during active suppression, mode-switch baseline update, and matching-reason finish semantics.

Reviews (10): Last reviewed commit: "Align window suppression actor isolation" | Re-trigger Greptile

Comment on lines +42 to +46
if activeWindowMoveSuppressionSequenceReason(window: window) == nil {
window.isMovable = !WorkspacePresentationModeSettings.isMinimal(defaults: defaults)
} else {
ensureWindowMoveSuppressionSequenceIsImmovable(window: window)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Stale previousMovableState after mode change mid-suppression

beginWindowMoveSuppressionSequence captures previousMovableState = window.isMovable at suppression start — now true in standard mode. When the mode transitions from standard → minimal while a suppression is active, this branch calls ensureWindowMoveSuppressionSequenceIsImmovable (correct during the drag) but never updates the stored previousMovableState. When the suppression ends, finishWindowMoveSuppressionSequence → restoreWindowDragging(previousMovableState: true) leaves the window movable, even though minimal mode expects isMovable = false. Before this PR this was harmless because previousMovableState was always false; the new standard-mode true makes it observable whenever a mode change fires mid-drag (e.g., via the command palette keyboard shortcut).

Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)

@lederniermagicien

Copy link
Copy Markdown
Contributor Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented May 29, 2026

Copy link
Copy Markdown

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@lederniermagicien I have started the AI code review. It will take a few minutes to complete.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

@lederniermagicien I’ll review the PR changes now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@lederniermagicien

Copy link
Copy Markdown
Contributor Author

Again pls @codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented May 29, 2026

Copy link
Copy Markdown

Again pls @codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@lederniermagicien I have started the AI code review. It will take a few minutes to complete.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

@lederniermagicien Sure, triggering another review of this PR now!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@lederniermagicien

Copy link
Copy Markdown
Contributor Author

Last one I hope @codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@cubic-dev-ai

cubic-dev-ai Bot commented May 29, 2026

Copy link
Copy Markdown

Last one I hope @codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@lederniermagicien I have started the AI code review. It will take a few minutes to complete.

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

@lederniermagicien Sure, triggering the review now — fingers crossed this is the last time you need to ask! 🤞

(ᵔᴥᵔ)🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@vercel

vercel Bot commented Jun 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 1, 2026 12:36am

@teamleaderleo teamleaderleo added the area: layout Splits, panes, tabs, windows, resizing, full screen label Sep 30, 2026
@teamleaderleo teamleaderleo added the S2: major A crash, hang, lost state, broken connection, or a regression on a path people use label Sep 30, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 2ebd1968 Deployed Jun 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: layout Splits, panes, tabs, windows, resizing, full screen S2: major A crash, hang, lost state, broken connection, or a regression on a path people use

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants