Skip to content

Fix unexpected menu-bar-only activation policy#6068

Merged
austinywang merged 15 commits into
mainfrom
issue-6065-dock-menubar-activation-policy
Jun 14, 2026
Merged

Fix unexpected menu-bar-only activation policy#6068
austinywang merged 15 commits into
mainfrom
issue-6065-dock-menubar-activation-policy

Conversation

@austinywang

@austinywang austinywang commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • require an explicit menu-bar-only opt-in marker before switching the app to .accessory
  • repair stale raw menuBarOnly=true defaults back to regular foreground mode only when the legacy command-palette history matches the isolated one-shot instant-toggle trap
  • remove the high-impact Menu Bar Only setting from command-palette instant toggles while preserving the Settings and cmux.json opt-in paths

Fixes #6065

Root cause / repro

Code inspection found a single production activation-policy writer: MenuBarOnlySettings.applyActivationPolicy, called at launch and on UserDefaults changes. PR #3181 introduced menu-bar-only mode by mapping menuBarOnly=true to .accessory; a later command-palette settings toggle exposed that same raw default as an instant command. A raw or accidental persisted menuBarOnly=true therefore leaves cmux in .accessory, which removes both the Dock icon and main menu bar.

Repro confirmed on cmux-aws-m4pro / aws-m4pro-2 (macOS 15.7.4) with real tagged cmux builds:

  • Pre-fix base 2a2d87bf81d81191005582e66e2f548c0aa406cf, seeded with menuBarOnly=true and commandPalette.commandUsage.v1 containing palette.toggleSetting.menuBarOnly, launched as com.cmuxterm.app.debug.repro6065before and settled to activationPolicy=accessory.
  • Current head dff725d2bc2519b63d36345d66ce7465074c3aba, seeded with the same isolated legacy defaults and useCount=1, stays activationPolicy=regular, and normalizes defaults to menuBarOnly=0 / menuBarOnlyExplicitlyEnabled.v1=0.
  • Current head preserves ambiguous or plausible intentional legacy opt-ins, including repeated command history and mixed command-palette history: activationPolicy=accessory, menuBarOnly=1, menuBarOnlyExplicitlyEnabled.v1=1.

Cloud-mac video was attempted but blocked by infrastructure: three default cloud-mac leases never surfaced a Tailscale host, the alternate macos-15 lease stayed queued, and the available AWS Macs denied full-screen recording via Screen Recording/TCC while raw VNC exposed Apple-only auth types unsupported by cua-vnc. The repro above is therefore confirmed by live AppKit activation-policy probing rather than video.

Testing

  • Confirmed repro/fix on macOS 15.7.4 with tagged Debug apps built remotely using CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag repro6065before, --tag repro6065after, and --tag repro6065current2.
  • python3 scripts/swift_file_length_budget.py passes after merging latest origin/main; the generated budget was refreshed for the current base.
  • Not run locally per issue instructions: no local tests/builds, no local xcodebuild, no local reload.sh; CI is the gate.

Localization

No new user-facing strings were added. Existing Settings labels/localized strings are reused; new literals are a hidden defaults marker and test JSON.

@vercel

vercel Bot commented Jun 14, 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 14, 2026 5:36am
cmux-staging Building Building Preview, Comment Jun 14, 2026 5:36am

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

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

Adds a two-key explicit-enable scheme (menuBarOnly + menuBarOnlyExplicitlyEnabled.v1) to MenuBarOnlySettings to prevent unintended .accessory activation policy. Introduces DefaultsValueModel.acceptCommittedValue, wires setMenuBarOnly through the SettingsHostActions protocol and host, normalizes legacy command palette history at startup, writes both keys on settings-file import, and removes the command-palette instant toggle.

Changes

Menu-bar-only confirmation guard

Layer / File(s) Summary
DefaultsValueModel acceptCommittedValue
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Bindings/DefaultsValueModel.swift, Packages/CmuxSettingsUI/Tests/CmuxSettingsUITests/DefaultsValueModelLifecycleTests.swift
Adds acceptCommittedValue(_:) to synchronously update current without a UserDefaultsSettingsStore write; adds a test asserting model state updates while the underlying UserDefaults key stays unset.
Two-key persistence and legacy detection
Sources/App/MenuBarExtraController.swift, Sources/CommandPalette/CommandPaletteSettingsToggle.swift
Adds explicitEnableKey constant and rewrites isEnabled to require both keys (with legacy command palette usage fallback). Adds setEnabled(_:defaults:) to write both keys atomically. Extends MenuBarOnlySettings with legacy normalization helpers and removes the menuBarOnly instant-toggle descriptor from CommandPaletteSettingsToggleCommands.
SettingsHostActions protocol and host wiring
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift, Sources/HostSettingsActions.swift
Adds setMenuBarOnly(_:) -> Bool protocol requirement with a default no-op returning false; implements it in HostSettingsActions by delegating to MenuBarOnlySettings.setEnabled and returning true.
App section toggle integration
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
Updates the "Menu Bar Only" toggle binding setter to call hostActions.setMenuBarOnly(enabled) and commits the local model via acceptCommittedValue only when the host action succeeds.
File store import and app startup normalization
Sources/KeyboardShortcutSettingsFileStore.swift, Sources/AppDelegate.swift
Writes explicitEnableKey = true when menuBarOnly: true is parsed from a settings file. Calls normalizeLegacyStoredPreference() in applicationDidFinishLaunching and syncApplicationPresentationPreferences before activation policy is applied.
Tests and project wiring
cmuxTests/MenuBarOnlyActivationPolicyTests.swift, cmux.xcodeproj/project.pbxproj
Adds MenuBarOnlyActivationPolicyTests covering command contribution absence, activation policy under varied legacy history, settings-file import semantics, and normalization. Registers the new test file in the Xcode project.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AppSection
    participant HostSettingsActions
    participant MenuBarOnlySettings
    participant DefaultsValueModel

    User->>AppSection: toggles Menu Bar Only
    AppSection->>HostSettingsActions: setMenuBarOnly(enabled)
    HostSettingsActions->>MenuBarOnlySettings: setEnabled(enabled, defaults)
    MenuBarOnlySettings-->>MenuBarOnlySettings: write menuBarOnly key
    MenuBarOnlySettings-->>MenuBarOnlySettings: write explicitEnableKey
    HostSettingsActions-->>AppSection: returns true
    AppSection->>DefaultsValueModel: acceptCommittedValue(enabled)
    DefaultsValueModel-->>DefaultsValueModel: update current (no UserDefaults write)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Two keys now guard the menu bar gate,
No sneaky .accessory decides your fate.
Legacy history checked, normalization done,
The Dock icon stays — the rabbit has won!
With committed values and protocol in place,
cmux runs steady, right in its space. 🎉

🚥 Pre-merge checks | ✅ 20 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Fix unexpected menu-bar-only activation policy' clearly and specifically summarizes the main change—addressing the bug where the app unexpectedly switches to menu-bar-only mode and disappears from the Dock.
Linked Issues check ✅ Passed The PR fully addresses all coding requirements from issue #6065 [#6065]: introduces explicit opt-in gating via menuBarOnlyExplicitlyEnabled.v1, repairs legacy stale defaults by checking command-palette history, removes the command-palette instant toggle, and includes regression test coverage via MenuBarOnlyActivationPolicyTests.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to the linked issue #6065 [#6065]: settings model updates (DefaultsValueModel.acceptCommittedValue), menu-bar-only logic gating (MenuBarOnlySettings), command palette settings exposure (removed toggle descriptor), host actions wiring (HostSettingsActions.setMenuBarOnly), and comprehensive test coverage for the fix.
Cmux Swift Actor Isolation ✅ Passed PR uses proper MainActor isolation: SettingsHostActions and AppSection are @MainActor; DefaultsValueModel.acceptCommittedValue adds no isolation issues; MenuBarOnlySettings static methods are threa...
Cmux Swift Blocking Runtime ✅ Passed No blocking or timing-based synchronization primitives introduced in production code; all new code uses synchronous, non-blocking defaults and settings operations.
Cmux Expensive Synchronous Load ✅ Passed No expensive synchronous loaders added to main actor or interactive paths. New code only reads bounded UserDefaults and command history JSON at startup/on UserDefaults change, not on menu/command-p...
Cmux Cache Substitution Correctness ✅ Passed All persistence reads use fresh authoritative sources (UserDefaults) not cached values; cold/stale history fallbacks are properly handled with conservative defaults; atomic writes to multiple keys...
Cmux No Hacky Sleeps ✅ Passed All PR changes are in Swift or Xcode project config; the rule explicitly excludes Swift code (covered by swift-blocking-runtime.md) and applies only to TypeScript, JavaScript, shell, and non-Swift...
Cmux Algorithmic Complexity ✅ Passed The PR does not violate algorithmic complexity rules. New logic includes: (1) legacyCommandPaletteToggleWasUsed—a single UserDefaults lookup + JSON parse of bounded global command history with one...
Cmux Swift Concurrency ✅ Passed All new production code methods are synchronous with no legacy async patterns introduced: no DispatchQueue, Combine, or completion handlers. Allowed test-only methods follow XCTest patterns.
Cmux Swift @Concurrent ✅ Passed All added/modified Swift methods are synchronous with proper actor isolation. HostSettingsActions.setMenuBarOnly and DefaultsValueModel.acceptCommittedValue are @MainActor methods, MenuBarOnlySetti...
Cmux Swift File And Package Boundaries ✅ Passed All changes comply with swift-file-package-boundaries rules: no new oversized files, no 250+ additions to files >800 lines, MenuBarOnlySettings extension appropriately placed in command-palette con...
Cmux Swift Logging ✅ Passed No logging violations found. PR adds menu-bar-only mode fixes across 8 production files and 2 test files with zero instances of print, debugPrint, dump, or NSLog. No MainActor-isolation logging iss...
Cmux User-Facing Error Privacy ✅ Passed PR introduces no new user-facing error messages, alerts, or content exposing prohibited items; all user-facing strings reuse existing localized keys using safe generic terms.
Cmux Full Internationalization ✅ Passed No new user-facing strings added; all UI text reuses existing fully-localized strings from Localizable.xcstrings. New additions are internal APIs, keys, and test code (all exempt).
Cmux Swiftui State Layout ✅ Passed All SwiftUI changes use the approved @Observable pattern; new acceptCommittedValue() is a safe state update method; menuBarOnly binding uses proper user-interaction handling with no render-time mut...
Cmux Architecture Rethink ✅ Passed PR introduces a small, architectural fix with clear ownership: adds explicit menuBarOnlyExplicitlyEnabled.v1 marker to gate activation policy change, normalizes legacy state deterministically at ap...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add or materially change any standalone cmux-owned windows; only modifications are test files and an internal accessibility cache utility class, which are allowed exceptions.
Cmux Source Artifacts ✅ Passed All changed files are intentional source code, tests, configs, or budget tracking files. No generated logs, caches, build artifacts, DerivedData, or temp directories present.
Description check ✅ Passed The PR description comprehensively addresses the summary, testing methodology, root cause, reproduction steps, and localization impact.

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

✨ 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 issue-6065-dock-menubar-activation-policy

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.

@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 `@Sources/AppDelegate.swift`:
- Around line 1228-1229: The startup code in AppDelegate.swift at the specified
location duplicates the repair and sync pattern that already exists in
syncApplicationPresentationPreferences(). Replace the two separate calls to
MenuBarOnlySettings.repairUnconfirmedStoredPreference() and
syncActivationPolicy() with a single call to
syncApplicationPresentationPreferences() to centralize the repair-sync logic and
ensure all presentation preferences are synced consistently at startup.
🪄 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: c296962a-388a-42b9-8023-e6a3e77280ad

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2d87b and ecd7dc0.

📒 Files selected for processing (10)
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
  • Sources/App/MenuBarExtraController.swift
  • Sources/AppDelegate.swift
  • Sources/CommandPalette/CommandPaletteSettingsToggle.swift
  • Sources/HostSettingsActions.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • cmuxTests/CommandPaletteSettingsToggleTests.swift
  • cmuxTests/KeyboardShortcutSettingsFileStoreStartupTests.swift
  • cmuxTests/NotificationAndMenuBarTests.swift
💤 Files with no reviewable changes (1)
  • Sources/CommandPalette/CommandPaletteSettingsToggle.swift

Comment thread Sources/AppDelegate.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.

Actionable comments posted: 3

🤖 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/App/MenuBarExtraController.swift`:
- Around line 524-526: The isEnabled(defaults:) method in
MenuBarExtraController.swift returns a computed value based on legacy keys when
the explicit marker is missing, but it does not persist any repair to the
defaults dictionary. This causes the Settings UI in AppSection.swift to read
stale key values and display outdated state. When the function takes the legacy
fallback path and returns !legacyCommandPaletteToggleWasUsed(defaults:), you
must also backfill or clear the menuBarOnlyKey and explicitEnableKey in the
defaults object to match the computed result. This ensures that all subsequent
reads of these keys by Settings bindings and other readers see the same
authoritative, up-to-date state instead of persisting stale opt-in signals.

In `@Sources/CommandPalette/CommandPaletteSettingsToggle.swift`:
- Around line 8-10: The legacyCommandPaletteToggleWasUsed function returns false
when the history data exists but fails to deserialize, which causes
MenuBarOnlySettings.isEnabled to incorrectly treat unreadable or corrupted
history as an implicit opt-out. This causes the app to silently fall back to
.accessory mode. Modify the function to only return true when the history data
is successfully parsed and explicitly contains the command palette usage marker;
when deserialization fails or data is unreadable, the function should not
implicitly signal that the toggle was used, ensuring the app stays in .regular
mode by default unless an explicit opt-in marker is confirmed.

In `@Sources/KeyboardShortcutSettingsFileStore.swift`:
- Line 424: Split the compound statement at line 424 in the
KeyboardShortcutSettingsFileStore.swift file that assigns values to both
MenuBarOnlySettings.menuBarOnlyKey and MenuBarOnlySettings.explicitEnableKey
into two separate lines. Currently, the two assignments are joined by a
semicolon on a single line. Separate them onto consecutive lines to match the
idiomatic Swift style and improve readability consistent with similar operations
elsewhere in the file.
🪄 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: be8061b1-e8ec-456e-981b-a663af5df456

📥 Commits

Reviewing files that changed from the base of the PR and between ecd7dc0 and 7600a4c.

📒 Files selected for processing (7)
  • Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
  • Sources/App/MenuBarExtraController.swift
  • Sources/CommandPalette/CommandPaletteSettingsToggle.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • cmuxTests/CommandPaletteSettingsToggleTests.swift
  • cmuxTests/KeyboardShortcutSettingsFileStoreMigrationTests.swift
  • cmuxTests/NotificationAndMenuBarTests.swift
💤 Files with no reviewable changes (1)
  • cmuxTests/NotificationAndMenuBarTests.swift

Comment thread Sources/App/MenuBarExtraController.swift Outdated
Comment thread Sources/CommandPalette/CommandPaletteSettingsToggle.swift Outdated
Comment thread Sources/KeyboardShortcutSettingsFileStore.swift Outdated
@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes an activation-policy regression (#6065) where a stale or accidentally-persisted menuBarOnly=true UserDefaults value would leave cmux in .accessory mode (no Dock icon, no main menu bar) without explicit user intent. The fix introduces a two-key protocol (menuBarOnlyKey + explicitEnableKey) and a one-shot migration (normalizeLegacyStoredPreference) that classifies legacy state at launch, then removes the high-impact Menu Bar Only entry from the command-palette instant-toggle surface.

  • Migration heuristic (legacyCommandPaletteOneShotLikelyEnabledMenuBarOnly): detects the exact pattern — sole command-palette-history entry for menuBarOnly, useCount == 1 — and resets to .regular; all ambiguous or plausible intentional states preserve .accessory.
  • Settings-UI write path (AppSectionhostActions.setMenuBarOnlysetEnabled): writes both keys atomically, then acceptCommittedValue updates the model without a second defaults write.
  • cmux.json opt-in (KeyboardShortcutSettingsFileStore): writes explicitEnableKey=true alongside menuBarOnlyKey=true; menuBarOnly=false intentionally leaves the explicit key untouched.

Confidence Score: 5/5

The PR is safe to merge: the migration is idempotent, the heuristic is conservative, and the two-key write protocol is atomic.

normalizeLegacyStoredPreference runs before the first syncActivationPolicy call, writes explicitEnableKey so all subsequent reads skip the heuristic, and the guard makes every re-entry a no-op. Tests cover the full heuristic decision tree including corrupt JSON, mixed history, and settings-file snapshot paths.

No files require special attention.

Important Files Changed

Filename Overview
Sources/CommandPalette/CommandPaletteSettingsToggle.swift Adds MenuBarOnlySettings extension with normalizeLegacyStoredPreference and the one-shot heuristic; removes the menuBarOnly instant-toggle descriptor from the command palette.
Sources/App/MenuBarExtraController.swift Adds explicitEnableKey and setEnabled to MenuBarOnlySettings; isEnabled now consults explicitEnableKey before falling back to the legacy heuristic.
Sources/AppDelegate.swift Calls normalizeLegacyStoredPreference before syncActivationPolicy at launch, and again inside syncApplicationPresentationPreferences (idempotent after first run).
Sources/KeyboardShortcutSettingsFileStore.swift When cmux.json sets menuBarOnly=true, also writes explicitEnableKey=true in the managed-defaults snapshot; menuBarOnly=false intentionally leaves explicitEnableKey untouched.
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift Menu Bar Only toggle routes through hostActions.setMenuBarOnly then acceptCommittedValue to update UI without a second defaults write.
cmuxTests/MenuBarOnlyActivationPolicyTests.swift New test suite covering all heuristic branches: isolated one-shot, mixed history, no history, repeated usage, corrupt JSON, settings-file opt-in, and config-false-does-not-clear-explicit-opt-in.

Reviews (12): Last reviewed commit: "merge: refresh file length budget after ..." | Re-trigger Greptile

Comment thread Sources/App/MenuBarExtraController.swift Outdated
Comment thread Sources/App/MenuBarExtraController.swift Outdated
Comment thread Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift Outdated
@austinywang austinywang merged commit 4d9e204 into main Jun 14, 2026
24 checks passed
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.

cmux disappears from the Dock and loses its menu bar (File/View/Window) while still running

1 participant