diff --git a/README.md b/README.md index d32e450..aa2acdd 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,14 @@ npx codeburn menubar One command: downloads the latest `.app`, installs into `~/Applications`, and launches it. Re-run with `--force` to reinstall. Native Swift + SwiftUI app lives in `mac/` (see `mac/README.md` for build details). Shows today's cost with a flame icon, opens a popover with agent tabs, period switcher (Today / 7 Days / 30 Days / Month / All), Trend / Forecast / Pulse / Stats / Plan insights, activity and model breakdowns, optimize findings, and CSV/JSON export. Refreshes live via FSEvents plus a 15-second poll. +**Compact mode** shrinks the menubar item to fit the text, dropping decimals (e.g. `$110` instead of `$110.20`). Opt in with: + +```bash +defaults write CodeBurnMenubar CodeBurnMenubarCompact -bool true +``` + +Relaunch the app to apply. To revert: `defaults delete CodeBurnMenubar CodeBurnMenubarCompact`. + ## What it tracks **13 task categories** classified from tool usage patterns and user message keywords. No LLM calls, fully deterministic. diff --git a/mac/Sources/CodeBurnMenubar/AppStore.swift b/mac/Sources/CodeBurnMenubar/AppStore.swift index 71d55dc..100647e 100644 --- a/mac/Sources/CodeBurnMenubar/AppStore.swift +++ b/mac/Sources/CodeBurnMenubar/AppStore.swift @@ -301,6 +301,11 @@ extension Double { let state = CurrencyState.shared return String(format: "\(state.symbol)%.2f", self * state.rate) } + + func asCompactCurrencyWhole() -> String { + let state = CurrencyState.shared + return "\(state.symbol)\(Int((self * state.rate).rounded()))" + } } extension Int { diff --git a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift index 501dcd6..e55aad6 100644 --- a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift +++ b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift @@ -7,6 +7,7 @@ private let nanosPerSecond: UInt64 = 1_000_000_000 private let refreshIntervalNanos: UInt64 = refreshIntervalSeconds * nanosPerSecond /// Fixed so the popover's anchor point doesn't shift each time today's cost changes. private let statusItemFixedWidth: CGFloat = 130 +private let statusItemCompactWidth: CGFloat = NSStatusItem.variableLength private let popoverWidth: CGFloat = 360 private let popoverHeight: CGFloat = 660 private let menubarTitleFontSize: CGFloat = 13 @@ -120,10 +121,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate { // MARK: - Status Item + private var isCompact: Bool { + UserDefaults.standard.bool(forKey: "CodeBurnMenubarCompact") + } + private func setupStatusItem() { - // Fixed width so the popover anchor (and thus popover position) doesn't shift - // every time today's cost or findings badge changes. - statusItem = NSStatusBar.system.statusItem(withLength: statusItemFixedWidth) + let width = isCompact ? statusItemCompactWidth : statusItemFixedWidth + statusItem = NSStatusBar.system.statusItem(withLength: width) guard let button = statusItem.button else { return } button.target = self button.action = #selector(handleButtonClick(_:)) @@ -152,20 +156,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate { let attachment = NSTextAttachment() attachment.image = flame if let size = flame?.size { - // Nudge the image down ~2pt so its visual centre sits on the text baseline mid-line - // rather than riding high. Exact value tuned against SF Pro Display 13pt. - attachment.bounds = CGRect(x: 0, y: -2, width: size.width, height: size.height) + attachment.bounds = CGRect(x: 0, y: -3, width: size.width, height: size.height) } let hasPayload = store.todayPayload != nil - let valueText = " " + (store.todayPayload?.current.cost.asCompactCurrency() ?? "$—") + let compact = isCompact + let fallback = compact ? "$-" : "$—" + let formatted = store.todayPayload?.current.cost + let valueText = compact + ? (formatted?.asCompactCurrencyWhole() ?? fallback) + : " " + (formatted?.asCompactCurrency() ?? fallback) let color: NSColor = hasPayload ? .labelColor : .secondaryLabelColor let composed = NSMutableAttributedString() composed.append(NSAttributedString(attachment: attachment)) composed.append(NSAttributedString( string: valueText, - attributes: [.font: font, .foregroundColor: color] + attributes: [.font: font, .foregroundColor: color, .baselineOffset: -1.0] )) button.attributedTitle = composed // Force immediate redraw. NSStatusItem sometimes defers the status bar paint for an