@@ -17,19 +17,30 @@ class AppDelegate: NSObject, NSApplicationDelegate {
1717 var usageManager : UsageManager ?
1818 var settingsWindow : NSWindow ?
1919 var currentUsage : UsageInfo ?
20+ var currentError : UsageError ?
2021 var settingsCancellable : AnyCancellable ?
2122
2223 func applicationDidFinishLaunching( _ notification: Notification ) {
2324 setupMenuBar ( )
24- usageManager = UsageManager { [ weak self] usage in
25- self ? . currentUsage = usage
26- self ? . updateMenuBar ( with: usage)
27- }
25+ usageManager = UsageManager (
26+ onUpdate: { [ weak self] usage in
27+ self ? . currentUsage = usage
28+ self ? . updateMenuBar ( with: usage)
29+ } ,
30+ onError: { [ weak self] error in
31+ self ? . currentError = error
32+ self ? . updateMenuBarForError ( error)
33+ }
34+ )
2835 usageManager? . startPolling ( )
2936
3037 // Listen for settings changes
3138 settingsCancellable = SettingsManager . shared. $displayMode. sink { [ weak self] _ in
32- self ? . updateMenuBar ( with: self ? . currentUsage)
39+ if self ? . currentError != nil {
40+ self ? . updateMenuBarForError ( self ? . currentError)
41+ } else {
42+ self ? . updateMenuBar ( with: self ? . currentUsage)
43+ }
3344 }
3445 }
3546
@@ -50,6 +61,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
5061 private func setupMenu( ) {
5162 let menu = NSMenu ( )
5263
64+ let errorItem = NSMenuItem ( title: " " , action: nil , keyEquivalent: " " )
65+ errorItem. isEnabled = false
66+ errorItem. tag = 99
67+ errorItem. isHidden = true
68+ menu. addItem ( errorItem)
69+
5370 let sessionItem = NSMenuItem ( title: " Session: -- " , action: nil , keyEquivalent: " " )
5471 sessionItem. isEnabled = false
5572 sessionItem. tag = 100
@@ -129,11 +146,75 @@ class AppDelegate: NSObject, NSApplicationDelegate {
129146 return Double ( numStr)
130147 }
131148
149+ private func updateMenuBarForError( _ error: UsageError ? ) {
150+ DispatchQueue . main. async { [ weak self] in
151+ guard let self = self , let button = self . statusItem? . button else { return }
152+
153+ // Update error menu item
154+ if let menu = self . statusItem? . menu, let errorItem = menu. item ( withTag: 99 ) {
155+ if let error = error {
156+ errorItem. title = error. message
157+ errorItem. isHidden = false
158+ } else {
159+ errorItem. title = " "
160+ errorItem. isHidden = true
161+ }
162+ }
163+
164+ if error != nil {
165+ // Show error state with warning icon
166+ let image = self . createErrorIcon ( )
167+ image. isTemplate = false
168+ button. image = image
169+ button. title = " CC: "
170+ button. imagePosition = . imageRight
171+
172+ // Also clear the usage display items
173+ if let menu = self . statusItem? . menu,
174+ let sessionItem = menu. item ( withTag: 100 ) ,
175+ let weeklyItem = menu. item ( withTag: 101 ) {
176+ sessionItem. title = " Session: -- "
177+ weeklyItem. title = " Weekly: -- "
178+ }
179+ } else {
180+ // Error cleared, restore normal display
181+ self . currentError = nil
182+ self . updateMenuBar ( with: self . currentUsage)
183+ }
184+ }
185+ }
186+
187+ private func createErrorIcon( ) -> NSImage {
188+ let size = NSSize ( width: 18 , height: 18 )
189+ let image = NSImage ( size: size, flipped: false ) { rect in
190+ // Draw warning triangle with exclamation mark
191+ let warningColor = NSColor ( red: 1.0 , green: 0.3 , blue: 0.3 , alpha: 1.0 )
192+
193+ // Use SF Symbol if available
194+ if let symbolImage = NSImage ( systemSymbolName: " exclamationmark.triangle.fill " , accessibilityDescription: " Error " ) {
195+ let config = NSImage . SymbolConfiguration ( pointSize: 14 , weight: . medium)
196+ let configuredImage = symbolImage. withSymbolConfiguration ( config)
197+
198+ // Draw with tint color
199+ warningColor. set ( )
200+ let imageRect = NSRect ( x: ( rect. width - 16 ) / 2 , y: ( rect. height - 16 ) / 2 , width: 16 , height: 16 )
201+ configuredImage? . draw ( in: imageRect, from: . zero, operation: . sourceOver, fraction: 1.0 )
202+
203+ // Apply color tint by drawing over
204+ NSGraphicsContext . current? . cgContext. setBlendMode ( . sourceAtop)
205+ warningColor. setFill ( )
206+ imageRect. fill ( )
207+ }
208+ return true
209+ }
210+ return image
211+ }
212+
132213 @objc func openSettings( ) {
133214 if settingsWindow == nil {
134215 let view = SettingsView ( )
135216 settingsWindow = NSWindow (
136- contentRect: NSRect ( x: 0 , y: 0 , width: 280 , height: 140 ) ,
217+ contentRect: NSRect ( x: 0 , y: 0 , width: 400 , height: 280 ) ,
137218 styleMask: [ . titled, . closable] ,
138219 backing: . buffered,
139220 defer: false
0 commit comments