Skip to content

Fix titlebar icon ownership and chrome alignment - #3883

Closed
lawrencecchen wants to merge 2 commits into
mainfrom
task-traffic-lights-shift
Closed

lawrencecchen wants to merge 2 commits into
mainfrom
task-traffic-lights-shift

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • keep the folder drag icon inline in the SwiftUI titlebar instead of a detached child panel
  • move traffic lights and sidebar/titlebar controls up slightly with stable, non-accumulating offsets
  • keep traffic-light offset state in the decorations controller instead of associated objects on AppKit private buttons

Verification

  • ./scripts/reload.sh --tag tfix
  • attempted cmux-unit focused test; hosted AppKit test runner crashed before assertions, so no unit test was kept for this UI placement change

Note

Medium Risk
Touches AppKit titlebar chrome positioning and traffic-light button frame adjustments, which can regress window controls across macOS versions. Scope is localized to UI/layout with no data/auth changes.

Overview
Keeps the focused-directory folder drag icon inline in the SwiftUI titlebar by removing the detached child NSPanel hosting approach and rendering DraggableFolderNSView directly.

Adjusts minimal-mode titlebar chrome alignment by nudging left controls and traffic lights upward, and makes traffic-light offsets non-accumulating by tracking per-button base/applied frames inside WindowDecorationsController via an NSMapTable (replacing associated-object storage on the buttons).

Reviewed by Cursor Bugbot for commit f79dac5. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented May 12, 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 May 12, 2026 6:50am
cmux-staging Building Building Preview, Comment May 12, 2026 6:50am

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR removes traffic-light debug offset support from WindowDecorationsController and migrates minimal-mode titlebar inset debug settings to persisted UserDefaults keys with AppStorage synchronization. Debug UI controls, snapshot/payload handling, geometry stability tests, and supporting localization and scripts are added.

Changes

Traffic-light offset removal & minimal-mode inset persistence

Layer / File(s) Summary
WindowDecorationsController traffic-light offset removal
Sources/WindowDecorationsController.swift
Remove associated-object key, debug offset application in apply(to:), helper methods, and TrafficLightDebugFrameState type. Traffic-light frames are no longer adjusted by this controller.
MinimalModeTitlebarDebugSettings migration to UserDefaults keys
Sources/WindowDragHandleView.swift
Add UserDefaults keys and clamped accessor functions for left-controls and traffic-light leading insets; remove X/Y offset defaults and ranges; update snapshot(defaults:) and reset(defaults:) to persist/restore leading insets; update MinimalModeTitlebarDebugSnapshot fields and change defaultLeftControlsTopInset to -4.0.
Persist insets via AppStorage in ContentView and VerticalTabsSidebar
Sources/ContentView.swift
Add @AppStorage properties for left-controls leading/top and traffic-light tab-bar/titlebar leading insets; construct titlebarDebugChromeSnapshot from persisted/clamped values; use snapshot for traffic-light inset syncing and sidebar controls padding.
Sidebar debug UI controls and payload inclusion
Sources/cmuxApp.swift
Add @AppStorage-backed sliders and reset button for minimal-mode titlebar insets in SidebarDebugView; capture titlebar snapshot in DebugWindowConfigSnapshot.combinedPayload and include four minimalModeTitlebar fields in both combined payload and sidebar copy config.
UI tests: inset sourcing, stability, and geometry regression
cmuxTests/WindowAndDragTests.swift
Rename tests to reflect debug-driven defaults; update sidebar fallback hit test to source insets from UserDefaults; add assertions validating snapshot reflects custom values and clamps extremes; add stability test for minimal-mode titlebar click target across repeated decoration application; add regression test verifying native traffic-light frames remain unchanged; introduce restoreDefaultsValue helper for test cleanup.
Localization strings and debug snapshot script
Resources/Localizable.xcstrings, skills/cmux-debug-windows/scripts/debug_windows_snapshot.sh
Add new localization entries for sidebar debug titlebar controls and reset button (English and Japanese); extend debug snapshot script to read and emit four minimalModeTitlebar inset numeric values in payload.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#3436: Directly related—both PRs modify WindowDecorationsController by removing traffic-light button offset logic and state.
  • manaflow-ai/cmux#3757: Related—both PRs modify MinimalModeTitlebarDebugSettings and MinimalModeTitlebarDebugSnapshot in WindowDragHandleView.swift to adjust persisted debug inset keys and snapshot fields.
  • manaflow-ai/cmux#3400: Related—both PRs modify SidebarDebugView debug UI and Localizable.xcstrings entries for minimal-mode titlebar controls.

Poem

🐰 Offsets be gone, insets now stored!
Where traffic lights once danced and swayed,
UserDefaults now holds the way,
And sliders let the debug truth be poured. ✨


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Logging ❌ Error PR adds unguarded NSLog in production code without #if DEBUG or Providers/ exception: ContentView.swift feedback error and cmuxApp.swift user action logging. Violates swift-logging.md rules. Guard with #if DEBUG or use Logger(subsystem:category:). Remove unguarded NSLog per swift-logging.md.
Cmux Swiftui State Layout ❌ Error PR introduces new ObservableObject with @Published where @Observable is the modern pattern, violating swiftui-state-layout.md rule for cmux-owned SwiftUI state. Replace MinimalModeSidebarChromeHoverState with @Observable, or use value snapshots + closures. Update HiddenTitlebarSidebarControlsView to use modern observation instead of .onReceive on @Published.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (12 passed)
Check name Status Explanation
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 No Swift 6 actor isolation violations introduced. Pure value structs have no isolation needs; @AppStorage uses are properly in MainActor SwiftUI Views; no new shared mutable state without isolation.
Cmux Swift Blocking Runtime ✅ Passed PR introduces no new blocking/timing-based synchronization. Changes are debug UI additions, @AppStorage declarations, and test methods. Pre-existing NSLock and Task.sleep patterns remain unmodified.
Cmux No Hacky Sleeps ✅ Passed PR violates no runtime timing rules. Only in-scope file is debug_windows_snapshot.sh, which adds configuration reading lines with no sleep/delay/polling patterns.
Cmux Swift Concurrency ✅ Passed PR introduces no new legacy async patterns. Changes add @AppStorage (SwiftUI, not Combine), refactor UserDefaults, and add UI controls. Task calls follow AppKit boundary exemptions.
Cmux Swift @Concurrent ✅ Passed New async submitFeedback() in @MainActor View has explicit MainActor.run boundaries around network I/O. Conforms to allowed pattern for async work coordinating UI operations.
Cmux Swift File And Package Boundaries ✅ Passed No new oversized files. Max 105-line additions to existing oversized files (under 250 threshold). No new mixed responsibilities. All changes are debug UI/settings coherent with existing scopes.
Cmux Architecture Rethink ✅ Passed Removes frame-mutation code, replaces with inset-based UserDefaults architecture. No new timing delays, polling, or problematic observers. Tests verify traffic lights remain unmoved.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new standalone windows added. Changes modify existing window decoration and debug settings behavior via UserDefaults refactoring and debug UI controls.
Title check ✅ Passed The title 'Fix titlebar icon ownership and chrome alignment' relates to the PR's changes in window decorations and titlebar controls alignment, which aligns with the main objectives of the PR.
Description check ✅ Passed The pull request description provides a clear summary of changes, includes testing verification details, and addresses the key modifications to traffic light positioning and titlebar chrome.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-traffic-lights-shift

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 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes two independent improvements: it replaces the DetachedFolderDragIconHostView child-NSPanel approach (which required per-event observers to track parent-window moves/resizes) with a direct DraggableFolderNSView inline in the SwiftUI hierarchy, and it adjusts chrome alignment by tweaking defaultLeftControlsTopInset, defaultTrafficLightsYOffset, and a new additive titlebarControlsUpwardOffset constant in UpdateTitlebarAccessory.

  • Folder drag icon: DetachedFolderDragIconHostView and its child-NSPanel + NotificationCenter observers are entirely removed; DetachedFolderDragIcon now wraps DraggableFolderNSView directly as an NSViewRepresentable, and the compensating .padding(.leading, -6) in ContentView is dropped.
  • Traffic-light offset state: migrated from objc_setAssociatedObject keyed on a static var UInt8 to a strongly-held NSMapTable<NSButton, TrafficLightFrameState> on the controller, removing the Debug label from both the class and the function.
  • Alignment constants: defaultLeftControlsTopInset moved from −2 to −4, defaultTrafficLightsYOffset from 1.7 to 3.0, and a non-debug titlebarControlsUpwardOffset = 2.0 is unconditionally added to the sidebar-controls yOffset.

Confidence Score: 5/5

Safe to merge — the child-panel removal is a clean simplification and the offset arithmetic is correct.

All changes are narrowly scoped UI positioning fixes. The child-panel teardown is fully removed with no dangling observers or stale references. The NSMapTable-based frame-state storage is correctly lifecycle-bound to the button via weak keys, and the new alignment constants produce stable, non-accumulating offsets. The only gap is that updateNSView calls NSWorkspace.icon(forFile:) on every parent re-render rather than only when the directory actually changes, which is a performance concern but not a correctness issue.

Sources/DetachedFolderDragIcon.swift — updateNSView unconditionally reloads the folder icon from disk on every SwiftUI update cycle.

Important Files Changed

Filename Overview
Sources/DetachedFolderDragIcon.swift Removes ~153 lines of child-panel machinery; the representable now wraps DraggableFolderNSView directly. updateNSView calls updateIcon() unconditionally on every SwiftUI update cycle rather than only when the directory changes.
Sources/WindowDecorationsController.swift State storage migrated from objc_setAssociatedObject to an NSMapTable with weak keys; class renamed from TrafficLightDebugFrameState to TrafficLightFrameState. appliedFrame now reads button.frame after setFrameOrigin rather than constructing NSRect(origin: nextOrigin, …) — semantically equivalent since AppKit updates the frame synchronously.
Sources/WindowDragHandleView.swift Adds titlebarControlsUpwardOffset = 2.0 constant; adjusts defaultLeftControlsTopInset (−2 → −4) and defaultTrafficLightsYOffset (1.7 → 3.0) for alignment.
Sources/Update/UpdateTitlebarAccessory.swift Adds titlebarControlsUpwardOffset to the yOffset formula; the constant is baked into the computed yOffset that feeds TitlebarControlsLayoutSnapshot, so change-detection remains correct.
Sources/ContentView.swift Removes the compensating .padding(.leading, -6) that was needed to offset the old child-panel placement; otherwise unchanged.

Sequence Diagram

sequenceDiagram
    participant SUI as SwiftUI (ContentView)
    participant DFD as DetachedFolderDragIcon
    participant DFV as DraggableFolderNSView
    participant WS as NSWorkspace

    Note over SUI,WS: New inline approach (this PR)
    SUI->>DFD: makeNSView
    DFD->>DFV: init(directory:)
    DFV->>WS: icon(forFile:)
    WS-->>DFV: NSImage

    SUI->>DFD: updateNSView (any parent re-render)
    DFD->>DFV: "directory = newDir"
    DFD->>DFV: updateIcon()
    DFV->>WS: "icon(forFile:) <- called unconditionally"

    participant WDC as WindowDecorationsController
    participant BTN as NSButton (traffic light)
    participant MT as NSMapTable

    WDC->>MT: trafficLightFrameState(for: button)
    MT-->>WDC: TrafficLightFrameState (weak key)
    WDC->>BTN: currentFrameMatchesApplied?
    alt frame moved externally
        WDC->>BTN: setFrameOrigin(baseOrigin + offset)
    end
    WDC->>MT: "state.appliedFrame = button.frame"
Loading

Reviews (8): Last reviewed commit: "Move traffic lights slightly higher" | Re-trigger Greptile

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/WindowAndDragTests.swift`:
- Around line 816-822: Add a private helper function named
restoreDefaultsValue(_ value: Any?, forKey key: String, defaults: UserDefaults)
to the WindowAndDragTests test class so the defer call in
testWindowDecorationsDoNotMoveNativeTrafficLights compiles; the method should
set the provided value into the given UserDefaults when value is non-nil and
remove the object for the key when value is nil (mirror the suggested behavior),
ensuring the helper is marked private and has the same signature used in the
test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8cbfdee6-f084-4f19-8143-f07c99ea6f5a

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce2a24 and 506bd4d.

📒 Files selected for processing (3)
  • Sources/WindowDecorationsController.swift
  • Sources/WindowDragHandleView.swift
  • cmuxTests/WindowAndDragTests.swift
💤 Files with no reviewable changes (1)
  • Sources/WindowDecorationsController.swift

Comment thread cmuxTests/WindowAndDragTests.swift Outdated

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

♻️ Duplicate comments (1)
cmuxTests/WindowAndDragTests.swift (1)

816-863: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add the missing restoreDefaultsValue helper function to avoid compilation error.

Line 821 calls restoreDefaultsValue(savedMode, forKey:defaults:), but this function is not defined in WindowAndDragTests.swift. The code will fail to compile.

Add the helper function to the test class:

Suggested implementation
private func restoreDefaultsValue(_ value: Any?, forKey key: String, defaults: UserDefaults) {
    if let value {
        defaults.set(value, forKey: key)
    } else {
        defaults.removeObject(forKey: key)
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/WindowAndDragTests.swift` around lines 816 - 863, Add a private
helper named restoreDefaultsValue(_ value: Any?, forKey key: String, defaults:
UserDefaults) to the test class so the call in
testMinimalModeSidebarTitlebarClickTargetUsesStableRaisedFrame compiles; the
helper should set the value for the key when value is non-nil and remove the key
when value is nil (i.e., if let value { defaults.set(value, forKey: key) } else
{ defaults.removeObject(forKey: key) }) to properly restore UserDefaults state
after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@cmuxTests/WindowAndDragTests.swift`:
- Around line 816-863: Add a private helper named restoreDefaultsValue(_ value:
Any?, forKey key: String, defaults: UserDefaults) to the test class so the call
in testMinimalModeSidebarTitlebarClickTargetUsesStableRaisedFrame compiles; the
helper should set the value for the key when value is non-nil and remove the key
when value is nil (i.e., if let value { defaults.set(value, forKey: key) } else
{ defaults.removeObject(forKey: key) }) to properly restore UserDefaults state
after the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 51fa192c-83bc-436f-bc4b-24f90641cda4

📥 Commits

Reviewing files that changed from the base of the PR and between 506bd4d and 3b81996.

📒 Files selected for processing (2)
  • Sources/WindowDragHandleView.swift
  • cmuxTests/WindowAndDragTests.swift

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/DetachedFolderDragIcon.swift`:
- Around line 51-59: The setFrameOrigin(_:) and setFrameSize(_:) overrides call
syncDetachedIconFrame() while frame/bounds change observers (enabled via
postsFrameChangedNotifications and NotificationCenter) also call it, causing
duplicate invocations; pick one synchronization path and remove the other to
simplify behavior: either delete the overrides setFrameOrigin and setFrameSize
and rely on the NotificationCenter observers, or keep the overrides and remove
the observers/disable postsFrameChangedNotifications; ensure
syncDetachedIconFrame() (which already has a guard to avoid redundant work)
continues to be called from the chosen path and update any observer
registration/removal code accordingly (e.g., the NotificationCenter
addObserver/removeObserver references).
- Around line 150-164: The code currently registers both
NSView.frameDidChangeNotification and NSView.boundsDidChangeNotification in
installHostGeometryObservers to call syncDetachedIconFrame; remove
NSView.boundsDidChangeNotification (and any code that sets
postsBoundsChangedNotifications on the host view) so only frame changes drive
the child-window sync, keeping hostGeometryObservers mapping and the closure
that calls MainActor.assumeIsolated { self?.syncDetachedIconFrame() } intact; if
you expect transforms/flipping in the host view, keep the bounds observer,
otherwise eliminate it to reduce unnecessary notifications.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f55cef1b-e156-4d46-85a5-bca1f5117717

📥 Commits

Reviewing files that changed from the base of the PR and between a4ac7c6 and 3fd3169.

📒 Files selected for processing (3)
  • Sources/ContentView.swift
  • Sources/DetachedFolderDragIcon.swift
  • cmuxTests/WindowAndDragTests.swift

Comment thread Sources/DetachedFolderDragIcon.swift Outdated
Comment thread Sources/DetachedFolderDragIcon.swift Outdated

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3fd3169. Configure here.

Comment thread Sources/WindowDragHandleView.swift Outdated
@lawrencecchen
lawrencecchen force-pushed the task-traffic-lights-shift branch from b984be9 to 90cdd7a Compare May 12, 2026 04:31
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@lawrencecchen lawrencecchen changed the title Keep traffic lights stationary Fix titlebar icon ownership and chrome alignment May 12, 2026
Comment on lines +372 to +401
private func applyTrafficLightOffsets(to window: NSWindow) {
let snapshot = MinimalModeTitlebarDebugSettings.snapshot()
let offset = NSPoint(
x: CGFloat(snapshot.trafficLightsXOffset),
y: CGFloat(snapshot.trafficLightsYOffset)
)
for buttonType in [NSWindow.ButtonType.closeButton, .miniaturizeButton, .zoomButton] {
guard let button = window.standardWindowButton(buttonType) else { continue }
let state = trafficLightFrameState(for: button)
let baseOrigin = state.currentFrameMatchesApplied(button.frame)
? state.baseOrigin
: button.frame.origin
let nextOrigin = NSPoint(x: baseOrigin.x + offset.x, y: baseOrigin.y + offset.y)
if abs(button.frame.origin.x - nextOrigin.x) > 0.25
|| abs(button.frame.origin.y - nextOrigin.y) > 0.25 {
button.setFrameOrigin(nextOrigin)
}
state.baseOrigin = baseOrigin
state.appliedFrame = button.frame
}
}

private func trafficLightFrameState(for button: NSButton) -> TrafficLightFrameState {
if let state = trafficLightFrameStates.object(forKey: button) {
return state
}
let state = TrafficLightFrameState(baseOrigin: button.frame.origin, appliedFrame: button.frame)
trafficLightFrameStates.setObject(state, forKey: button)
return state
}

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.

P1 Promised regression test absent from PR

The PR description states "Regression test added in the first commit — testWindowDecorationsDoNotMoveNativeTrafficLights" but the PR contains a single commit ("Fix titlebar icon ownership and chrome alignment") and no test file appears anywhere in the diff. A search of cmuxTests/ confirms no such function exists. The regression safety net that was the stated justification for the change is missing.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

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

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants