Skip to content

Commit 96a4591

Browse files
committed
Restore status controller factory and icon nil handling
1 parent 5eddcc0 commit 96a4591

File tree

3 files changed

+41
-27
lines changed

3 files changed

+41
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 0.4.3 — 2025-11-21
44
- Fix status item creation timing on macOS 15 by deferring NSStatusItem setup to after launch; adds a regression test for the path.
5+
- Menu bar icon with unknown usage now draws empty tracks (instead of a full bar when decorations are shown) by treating nil values as 0%.
56

67
## 0.4.2 — 2025-11-21
78
- Sparkle updates re-enabled in release builds (disabled only for the debug bundle ID).

Sources/CodexBar/CodexbarApp.swift

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ private func makeUpdaterController() -> UpdaterProviding {
120120
@MainActor
121121
final class AppDelegate: NSObject, NSApplicationDelegate {
122122
let updaterController: UpdaterProviding = makeUpdaterController()
123-
private var statusController: StatusItemController?
123+
private var statusController: StatusItemControlling?
124124
private var store: UsageStore?
125125
private var settings: SettingsStore?
126126
private var account: AccountInfo?
@@ -138,33 +138,29 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
138138
}
139139

140140
private func ensureStatusController() {
141-
if self.statusController != nil {
141+
if self.statusController != nil { return }
142+
143+
if let store, let settings, let account, let selection = self.preferencesSelection {
144+
self.statusController = StatusItemController.factory(
145+
store,
146+
settings,
147+
account,
148+
self.updaterController,
149+
selection)
142150
return
143151
}
144152

145-
if let store = self.store,
146-
let settings = self.settings,
147-
let account = self.account,
148-
let selection = self.preferencesSelection
149-
{
150-
self.statusController = StatusItemController(
151-
store: store,
152-
settings: settings,
153-
account: account,
154-
updater: self.updaterController,
155-
preferencesSelection: selection)
156-
} else {
157-
let settings = SettingsStore()
158-
let fetcher = UsageFetcher()
159-
let account = fetcher.loadAccountInfo()
160-
let store = UsageStore(fetcher: fetcher, settings: settings)
161-
self.statusController = StatusItemController(
162-
store: store,
163-
settings: settings,
164-
account: account,
165-
updater: self.updaterController,
166-
preferencesSelection: PreferencesSelection())
167-
}
153+
// Defensive fallback: this should not be hit in normal app lifecycle.
154+
let fallbackSettings = SettingsStore()
155+
let fetcher = UsageFetcher()
156+
let fallbackAccount = fetcher.loadAccountInfo()
157+
let fallbackStore = UsageStore(fetcher: fetcher, settings: fallbackSettings)
158+
self.statusController = StatusItemController.factory(
159+
fallbackStore,
160+
fallbackSettings,
161+
fallbackAccount,
162+
self.updaterController,
163+
PreferencesSelection())
168164
}
169165
}
170166

@@ -182,8 +178,23 @@ extension CodexBarApp {
182178

183179
// MARK: - Status item controller (AppKit-hosted icons, SwiftUI popovers)
184180

181+
protocol StatusItemControlling: AnyObject {}
182+
185183
@MainActor
186-
final class StatusItemController: NSObject, NSMenuDelegate {
184+
final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControlling {
185+
typealias Factory = (UsageStore, SettingsStore, AccountInfo, UpdaterProviding, PreferencesSelection)
186+
-> StatusItemControlling
187+
static let defaultFactory: Factory = { store, settings, account, updater, selection in
188+
StatusItemController(
189+
store: store,
190+
settings: settings,
191+
account: account,
192+
updater: updater,
193+
preferencesSelection: selection)
194+
}
195+
196+
static var factory: Factory = StatusItemController.defaultFactory
197+
187198
private let store: UsageStore
188199
private let settings: SettingsStore
189200
private let account: AccountInfo

Sources/CodexBar/IconRenderer.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ enum IconRenderer {
4040
trackPath.lineWidth = 1.2
4141
trackPath.stroke()
4242

43-
guard let rawRemaining = remaining ?? (addNotches ? 100 : nil) else { return }
43+
// When remaining is unknown, do not render a full bar; draw only the track (and decorations) unless a value
44+
// exists.
45+
guard let rawRemaining = remaining ?? (addNotches ? 0 : nil) else { return }
4446
// Clamp fill because backend might occasionally send >100 or <0.
4547
let clamped = max(0, min(rawRemaining / 100, 1))
4648
let fillRect = CGRect(x: x, y: y, width: width * clamped, height: height)

0 commit comments

Comments
 (0)