Skip to content

Menu bar icon position resets to leftmost after enabling/disabling a provider #538

@hxy91819

Description

@hxy91819

Summary

Every time a provider is enabled or disabled, all menu bar icons jump back to the leftmost position, losing the custom position set via Cmd+Drag.

Steps to Reproduce

  1. Launch CodexBar with 2+ providers enabled (separate icon mode, not merged)
  2. Cmd+Drag the icons to a custom position in the menu bar
  3. Open Preferences → Providers, enable or disable any provider
  4. Observe the menu bar

Actual Result

All CodexBar status icons are recreated at the leftmost position. The user's custom arrangement is lost.

Expected Result

Existing icons should stay in their current position. Only newly added icons should appear at the default position.

Root Cause

rebuildProviderStatusItems() in StatusItemController.swift destroys all NSStatusItem instances and recreates them on every provider config change. macOS binds icon position to the NSStatusItem instance — once destroyed, the position is lost.

Additionally, NSStatusItem.autosaveName is never set, so macOS cannot persist and restore icon positions across app launches.

Suggested Fix

Two changes to Sources/CodexBar/StatusItemController.swift:

1. Set autosaveName on all NSStatusItem instances

Use stable identifiers ("codexbar-merged" for merged mode, "codexbar-{provider.rawValue}" for individual providers) so macOS can automatically persist positions via its built-in autosaveName mechanism (macOS 12+).

Three creation sites need this: init (merged item), lazyStatusItem(for:), and rebuildProviderStatusItems().

2. Incremental update in rebuildProviderStatusItems()

Replace the full destroy-and-recreate loop with incremental logic:

  • Only remove items for providers no longer in the desired set
  • Reuse existing items (do not touch them)
  • Only create items for newly added providers
private func rebuildProviderStatusItems() {
    let desiredOrder = self.settings.orderedProviders()
    let desiredSet = Set(desiredOrder)

    for (provider, item) in self.statusItems where !desiredSet.contains(provider) {
        self.statusBar.removeStatusItem(item)
        self.statusItems.removeValue(forKey: provider)
    }

    for provider in desiredOrder {
        if self.statusItems[provider] == nil {
            let item = self.statusBar.statusItem(withLength: NSStatusItem.variableLength)
            item.button?.imageScaling = .scaleNone
            item.autosaveName = "codexbar-\(provider.rawValue)"
            self.statusItems[provider] = item
        }
    }
}

Environment

  • CodexBar: 0.18.0 / main branch (commit f65017c)
  • macOS: 14.8.4 (Apple M3 Pro)

Note: I'm currently unable to run swift build / swift test since my macOS version doesn't yet support the required Swift 6.2
toolchain. Will follow up with build verification once my environment permits.

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedConfirmed backlog item or verified open bugarea:ui-uxUI polish, settings, layout, interaction behaviorbugSomething isn't workingpriority:mediumMedium priority: real issue or meaningful backlog item

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions