Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mac/Sources/CodeBurnMenubar/AppStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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") }
}
Expand Down
39 changes: 32 additions & 7 deletions mac/Sources/CodeBurnMenubar/CodeBurnApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion mac/Sources/CodeBurnMenubar/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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))
}
Expand Down