feat(menubar): polish the status-item menu and About tab (#509)

Right-click menu:
- Add a dimmed "Today · $X · N calls" usage row at the top.
- Give Settings an explicit gearshape icon (instead of relying on the macOS
  Tahoe auto-decoration).
- Add an "About CodeBurn" item that opens Settings on the About tab.
- Drop the per-item keyboard-shortcut labels and the section separators for a
  flat, compact list.
- Open the menu a few px below the status item so it clears the menu bar
  (anchoring flush clipped the top edge and triggered menu scrolling on hover).

Settings:
- Add a settingsTab selection to AppStore and bind SettingsView's TabView to
  it (tagged tabs) so "About CodeBurn" can jump straight to the About tab.
- About tab: add a "Star on GitHub" button and a "Sponsor" link.
This commit is contained in:
Resham Joshi 2026-06-18 14:25:29 +02:00 committed by GitHub
parent dbe4e94322
commit 96d74a04b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 8 deletions

View file

@ -52,6 +52,9 @@ final class AppStore {
}
var showingAccentPicker: Bool = false
var currency: String = "USD"
/// Which Settings tab to show; lets the menu's "About CodeBurn" item jump
/// straight to the About tab even when the Settings window is reused.
var settingsTab: String = "general"
var displayMetric: DisplayMetric = DisplayMetric(rawValue: UserDefaults.standard.string(forKey: "CodeBurnDisplayMetric") ?? "") ?? .cost {
didSet { UserDefaults.standard.set(displayMetric.rawValue, forKey: "CodeBurnDisplayMetric") }
}

View file

@ -857,31 +857,56 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
let menu = NSMenu()
let settingsItem = NSMenuItem(title: "Settings…", action: #selector(openSettings), keyEquivalent: ",")
// Glanceable "today" usage at the top as a single non-interactive (dimmed)
// row. A real .sectionHeader adds section padding (and pulls the actions
// into its group without a separator), so use a plain disabled item.
let usageItem = NSMenuItem(title: contextMenuUsageSummary(), action: nil, keyEquivalent: "")
usageItem.isEnabled = false
menu.addItem(usageItem)
let settingsItem = NSMenuItem(title: "Settings…", action: #selector(openSettings), keyEquivalent: "")
settingsItem.target = self
settingsItem.image = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "Settings")
menu.addItem(settingsItem)
let refreshNow = NSMenuItem(title: "Refresh Now", action: #selector(refreshNowAction), keyEquivalent: "r")
let refreshNow = NSMenuItem(title: "Refresh Now", action: #selector(refreshNowAction), keyEquivalent: "")
refreshNow.target = self
menu.addItem(refreshNow)
menu.addItem(.separator())
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")
let aboutItem = NSMenuItem(title: "About CodeBurn", action: #selector(openAbout), keyEquivalent: "")
aboutItem.target = self
menu.addItem(aboutItem)
let quitItem = NSMenuItem(title: "Quit CodeBurn", action: #selector(quitApp), keyEquivalent: "")
quitItem.target = self
menu.addItem(quitItem)
// Present directly. The previous `statusItem.menu = menu; button.performClick`
// trick relies on the click -> action path that macOS 27 changed; popUp is
// version-stable.
menu.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.height + 4), in: button)
// version-stable. Open a few px below the status item so the menu clears the
// menu bar: anchoring flush clips the top edge and makes macOS engage menu
// scrolling (a scroll chevron appears and the first row slides up on hover).
menu.popUp(positioning: nil, at: NSPoint(x: 0, y: button.bounds.height + 6), in: button)
}
/// One-line "today" summary for the context menu's usage row.
private func contextMenuUsageSummary() -> String {
guard let current = store.todayPayload?.current else { return "Today · no usage yet" }
let calls = current.calls == 1 ? "1 call" : "\(current.calls) calls"
return "Today · \(current.cost.asCurrency()) · \(calls)"
}
private var settingsWindowController: NSWindowController?
@objc private func openAbout() {
store.settingsTab = "about"
openSettings()
}
@objc private func openSettings() {
// Accessory-policy apps (no Dock icon, no main menu) don't get the
// SwiftUI Settings scene wired into the responder chain reliably, so

View file

@ -8,21 +8,26 @@ struct SettingsView: View {
@Environment(AppStore.self) private var store
var body: some View {
TabView {
TabView(selection: Binding(get: { store.settingsTab }, set: { store.settingsTab = $0 })) {
GeneralSettingsTab()
.tabItem { Label("General", systemImage: "gearshape") }
.tag("general")
ClaudeSettingsTab()
.tabItem { Label("Claude", systemImage: "brain") }
.tag("claude")
CodexSettingsTab()
.tabItem { Label("Codex", systemImage: "chevron.left.forwardslash.chevron.right") }
.tag("codex")
DevinSettingsTab()
.tabItem { Label("Devin", systemImage: "flame.fill") }
.tag("devin")
AboutSettingsTab()
.tabItem { Label("About", systemImage: "info.circle") }
.tag("about")
}
.frame(width: 520, height: 430)
}
@ -660,9 +665,16 @@ private struct AboutSettingsTab: View {
Text("Version \(appVersion) (\(buildVersion))")
.font(.codeMono(size: 11))
.foregroundStyle(.secondary)
Link(destination: URL(string: "https://github.com/getagentseal/codeburn")!) {
Label("Star on GitHub", systemImage: "star.fill")
.font(.system(size: 12, weight: .medium))
}
.buttonStyle(.borderedProminent)
.tint(Theme.brandAccent)
HStack(spacing: 10) {
Link("GitHub", destination: URL(string: "https://github.com/getagentseal/codeburn")!)
Link("Issues", destination: URL(string: "https://github.com/getagentseal/codeburn/issues")!)
Link("Sponsor", destination: URL(string: "https://github.com/sponsors/iamtoruk")!)
}
.font(.system(size: 12))
}