# How the CodexBar Provider Switcher UI Works: Architecture and Implementation

> Explore the CodexBar provider switcher UI architecture and implementation. Learn how it handles input and avoids re-entrancy bugs for a seamless user experience.

- Repository: [Peter Steinberger/CodexBar](https://github.com/steipete/CodexBar)
- Tags: architecture
- Published: 2026-07-07

---

**The CodexBar provider switcher UI is a self-contained, menu-driven component that renders toggle-style provider buttons with quota indicators inside the status menu, handling mouse and keyboard input through a custom event monitor to avoid re-entrancy bugs during menu rebuilds.**

CodexBar is an open-source macOS menu bar application that provides quick access to AI coding assistants. The provider switcher UI allows users to toggle between different AI providers directly from the status menu, implementing a complex three-layer architecture that balances visual responsiveness with robust event handling.

## Architecture Overview

The provider switcher implementation spans three distinct layers that coordinate to deliver smooth interactions within AppKit's menu system.

### View Layer: ProviderSwitcherView

The **view layer** is implemented in `StatusItemController+SwitcherViews.swift` and centers on the `ProviderSwitcherView` class. This view builds a row (or multi-row) of toggle-style buttons for each usage provider plus an optional "Overview" segment.

The view supports three distinct button styles defined in [`ProviderSwitcherButtons.swift`](https://github.com/steipete/CodexBar/blob/main/ProviderSwitcherButtons.swift):
- **PaddedToggleButton** – Standard padded toggle appearance
- **InlineIconToggleButton** – Compact style with inline icons
- **StackedToggleButton** – Vertical stack layout when icons are displayed

`ProviderSwitcherView` calculates layout metrics including outer padding, uniform or non-uniform segment widths, and row counts to ensure the switcher fits the menu width without squeezing text. It draws per-provider quota-usage bars using a track-and-fill view system underneath each button, updating colors based on the provider's branding extracted from `ProviderDescriptorRegistry.descriptor(for:).branding.color`.

To avoid disruptions from the default `NSButton` click loop while the menu is being rebuilt, the view handles mouse tracking manually through `acceptsFirstMouse`, `hitTest`, `mouseDown/Up`, `mouseMoved`, and `mouseExited`. It provides keyboard navigation via `handleKeyboardSelection(at:)` and `handleSelection(_:)` methods.

### Interaction Layer: Event Monitor

The **interaction layer** lives in `StatusItemController+ProviderSwitcher.swift` and consists of the `ProviderSwitcherShortcutEventMonitor` class. This monitor installs a run-loop observer that peeks at the event queue only while the menu tracking loop is alive, tracked by `ProviderSwitcherMenuTrackingState`.

A **peek gate** (`ProviderSwitcherEventPeekGate`) optimizes performance by comparing CG event counters to skip unnecessary peeks during mouse-move-heavy sessions, saving CPU work. The monitor forwards key-down, key-up, and scroll-wheel events to the view's `handleProviderSwitcherTrackingEvent` method, which routes them to `ProviderSwitcherView` for selection or scrolling the overview.

### Controller Layer: StatusItemController

The **controller layer**, also in `StatusItemController+ProviderSwitcher.swift`, manages the switcher lifecycle. When building the menu via `installProviderSwitcherShortcutMonitorIfNeeded`, the controller checks `shouldMergeIcons` and verifies if `menu.items.first?.view is ProviderSwitcherView` to determine if a switcher is needed.

It creates the monitor, binds it to the menu, and clears pending pointer interactions when the menu tears down. The controller exposes helper APIs including `selectProviderSwitcherSegment(at:)`, `navigateProviderSwitcher(direction:)`, and `requestProviderSwitcherMenuRebuild` that the view calls when selections change.

## User Interaction Flow

When a user clicks the CodexBar status menu icon, the following sequence executes:

1. `StatusItemController` builds the status menu dynamically
2. If the provider switcher is enabled, it inserts a `ProviderSwitcherView` as the first menu item
3. The view creates a button per provider (and optionally an Overview button) using the specified button style
4. When the user hovers over a segment, `mouseMoved` updates `hoveredButtonTag`, triggering `updateButtonStyles` to change the background color
5. On mouse-down/up events, the view's `handleMenuTrackingMouseDown/Up` validates the click and calls `applySelection(at:)`
6. `applySelection(at:)` updates the internal state, informs the controller via the `onSelect` closure, and the controller may rebuild the entire menu to reflect the new provider

## Keyboard Navigation

Arrow keys and number keys are intercepted by the `ProviderSwitcherShortcutEventMonitor` rather than using standard menu key handling. The monitor peeks the event queue *without* re-entering the menu's event-tracking run-loop, preventing re-entrancy bugs that historically caused zombie menus to persist.

When a relevant key is detected, the monitor dispatches it to `StatusItemController.handleMenuTrackingShortcutEvent`, which ultimately calls `ProviderSwitcherView.handleKeyboardSelection(at:)` to update the selection state and visual appearance.

## Quota Indicator Rendering

Each provider button displays usage quota through a thin indicator bar. The `addQuotaIndicator(to:selection:remainingPercent:)` method creates a track view and a fill view, setting the fill view's width constraint to `ratio * track.width` based on the remaining percentage.

The fill color derives from the provider's branding color. To keep the UI uncluttered, the indicator automatically hides when its button is selected, ensuring only unselected providers show quota usage.

## Implementation Examples

When constructing the provider switcher in `StatusItemController`, you initialize the view with providers, selection state, and callbacks:

```swift
let switcher = ProviderSwitcherView(
    providers: usageProviders,
    selected: currentSelection,
    includesOverview: true,
    width: menuWidth,
    showsIcons: true,
    iconProvider: { provider in
        // Obtain the 16 pt icon for the provider
        ProviderIconCache.icon(for: provider)
    },
    weeklyRemainingProvider: { provider in
        // Return remaining weekly quota percent or nil
        UsageQuotaStore.weeklyRemainingPercent(for: provider)
    },
    onSelect: { selection in
        // Switch the active provider and rebuild the menu
        self.setActiveProvider(selection)
    })
menuItem.view = switcher

```

The event monitor installation follows this pattern:

```swift
let monitor = ProviderSwitcherShortcutEventMonitor(
    events: [.keyDown, .keyUp, .scrollWheel]) { [weak self] event in
        guard let self, let menu = self.currentMenu else { return false }
        return self.handleMenuTrackingShortcutEvent(event, menu: menu)
}
monitor.start()

```

For unit testing interaction handling, the view exposes a simulation method:

```swift
let view = ProviderSwitcherView(...)   // constructed in a test
_ = view._test_simulateRuntimeClick(buttonTag: 2)   // selects provider at index 2
XCTAssertEqual(view.selectedSegmentIndex, 2)

```

## Summary

- The **CodexBar provider switcher UI** implements a three-layer architecture separating view rendering (`ProviderSwitcherView`), event handling (`ProviderSwitcherShortcutEventMonitor`), and coordination (`StatusItemController`).
- Mouse handling is implemented manually in `StatusItemController+SwitcherViews.swift` to avoid `NSButton` click loop disruptions during menu rebuilds.
- The **peek gate** optimization in `ProviderSwitcherEventPeekGate` prevents unnecessary event processing during heavy mouse movement.
- Quota indicators use dynamic width constraints based on `UsageQuotaStore.weeklyRemainingPercent(for:)` and hide when their associated button is selected.
- Keyboard navigation bypasses standard menu handling to prevent re-entrancy bugs that could leave zombie menus visible.

## Frequently Asked Questions

### How does CodexBar prevent menu re-entrancy bugs when switching providers?

The `ProviderSwitcherShortcutEventMonitor` peeks at the event queue using a run-loop observer without re-entering the menu's event-tracking run-loop. This approach prevents the re-entrancy issues that historically caused zombie menus to persist after switching providers. The monitor forwards relevant events to `StatusItemController.handleMenuTrackingShortcutEvent`, which safely updates the selection without disrupting the menu's internal state.

### What determines the button style in the provider switcher?

The button style depends on configuration flags passed to `ProviderSwitcherView` during initialization. The view chooses between `PaddedToggleButton`, `InlineIconToggleButton`, or `StackedToggleButton` (defined in [`ProviderSwitcherButtons.swift`](https://github.com/steipete/CodexBar/blob/main/ProviderSwitcherButtons.swift)) based on whether icons are enabled and whether they should be displayed in a stacked layout. This selection happens during the view's layout calculation phase in `StatusItemController+SwitcherViews.swift`.

### Why are quota indicators hidden when a provider is selected?

The `addQuotaIndicator(to:selection:remainingPercent:)` method intentionally hides the quota fill view when the button's selection state is active. This design choice keeps the UI uncluttered by removing the usage bar from the currently active provider, allowing users to focus on the remaining quota of inactive providers at a glance. The indicator reappears automatically when the provider is deselected.

### How does the provider switcher maintain state during menu rebuilds?

The `StatusItemController+MergedSwitcherContentCache.swift` file implements a caching mechanism that preserves the `ProviderSwitcherView` instance while the menu is being rebuilt. This prevents flickering and maintains hover states during the transition between providers. The controller checks `shouldMergeIcons` and existing view types before deciding whether to create a new switcher or reuse the cached instance.