From f35400f19914cc47c88d1efcc65fa4ca07f07620 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Thu, 30 Apr 2026 09:22:26 -0700 Subject: [PATCH] Fix menubar refresh stuck after first load (#179) forceRefresh() was missing force:true, so the cache TTL guard silently skipped every LaunchAgent and wake-triggered refresh. Also adds right-click context menu and version label in footer. --- CHANGELOG.md | 5 ++ mac/Sources/CodeBurnMenubar/CodeBurnApp.swift | 63 ++++++++++++++++++- .../Views/MenuBarContent.swift | 6 +- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f6ccc..4820b56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,9 +26,14 @@ - **Instant cached data display.** Shows cached data immediately instead of blocking on CLI refresh. ### Fixed (macOS menubar) +- **Menubar stops updating after first load.** Background refresh was silently skipped by the cache TTL guard. Data loaded once, then froze. Fixes #179. - **Menubar not dimming on inactive screens.** - **Performance improvements.** Reduced unnecessary redraws and CLI invocations. +### Added (macOS menubar) +- **Right-click context menu.** Right-click the status bar icon for "Check for Updates" and "Quit CodeBurn". +- **Version label in footer.** + ### Changed - README restructured with honeycomb provider hero image, 2x2 screenshot grid, and complete inline reference. - `bunx codeburn` added as alternative install option. diff --git a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift index c1d002f..1c87221 100644 --- a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift +++ b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift @@ -174,7 +174,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate { lastRefreshTime = now Task { - await store.refresh(includeOptimize: true) + await store.refresh(includeOptimize: true, force: true) refreshStatusButton() } } @@ -311,7 +311,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate { } @objc private func handleButtonClick(_ sender: AnyObject?) { - guard let button = statusItem.button else { return } + guard let button = statusItem.button, + let event = NSApp.currentEvent else { return } + + if event.type == .rightMouseUp { + showContextMenu(from: button) + return + } + if popover.isShown { popover.performClose(sender) } else { @@ -321,6 +328,58 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate { } } + private func showContextMenu(from button: NSStatusBarButton) { + let menu = NSMenu() + let updateItem = NSMenuItem(title: "Check for Updates", action: #selector(checkForUpdates), keyEquivalent: "") + updateItem.target = self + menu.addItem(updateItem) + menu.addItem(.separator()) + let quitItem = NSMenuItem(title: "Quit CodeBurn", action: #selector(quitApp), keyEquivalent: "q") + quitItem.target = self + menu.addItem(quitItem) + statusItem.menu = menu + button.performClick(nil) + statusItem.menu = nil + } + + private func codeburnAlertIcon() -> NSImage? { + let config = NSImage.SymbolConfiguration(pointSize: 32, weight: .medium) + guard let symbol = NSImage(systemSymbolName: "flame.fill", accessibilityDescription: "CodeBurn")? + .withSymbolConfiguration(config) else { return nil } + let size = NSSize(width: 64, height: 64) + let img = NSImage(size: size, flipped: false) { rect in + let symbolSize = symbol.size + let x = (rect.width - symbolSize.width) / 2 + let y = (rect.height - symbolSize.height) / 2 + symbol.draw(in: NSRect(x: x, y: y, width: symbolSize.width, height: symbolSize.height)) + return true + } + img.isTemplate = false + return img + } + + @objc private func checkForUpdates() { + Task { + await updateChecker.check() + let alert = NSAlert() + alert.icon = codeburnAlertIcon() + if updateChecker.updateAvailable, let latest = updateChecker.latestVersion { + alert.messageText = "Update Available" + alert.informativeText = "v\(latest) is available (you have v\(updateChecker.currentVersion)). Run:\n\ncodeburn menubar --force" + } else { + alert.messageText = "Up to Date" + alert.informativeText = "You're on the latest version (v\(updateChecker.currentVersion))." + } + alert.alertStyle = .informational + alert.addButton(withTitle: "OK") + alert.runModal() + } + } + + @objc private func quitApp() { + NSApp.terminate(nil) + } + // MARK: - NSPopoverDelegate func popoverShouldDetach(_ popover: NSPopover) -> Bool { diff --git a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift index f067aa0..44879d8 100644 --- a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift +++ b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift @@ -422,8 +422,12 @@ struct FooterBar: View { Spacer() + Text("v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "?")") + .font(.system(size: 10, weight: .regular, design: .monospaced)) + .foregroundStyle(.tertiary) + Button { openReport() } label: { - Label("Open Full Report", systemImage: "terminal") + Label("Full Report", systemImage: "terminal") .font(.system(size: 11, weight: .semibold)) .labelStyle(.titleAndIcon) }