diff --git a/macos-bar/Sources/CCSBarApp/BarMenuView.swift b/macos-bar/Sources/CCSBarApp/BarMenuView.swift index b5d29124..3293883e 100644 --- a/macos-bar/Sources/CCSBarApp/BarMenuView.swift +++ b/macos-bar/Sources/CCSBarApp/BarMenuView.swift @@ -192,6 +192,11 @@ struct BarMenuView: View { Text("usage & accounts").font(.caption2).foregroundStyle(.secondary) } Spacer() + if let v = BarVersionDisplay.string() { + Text(v) + .font(.caption2) + .foregroundStyle(.secondary) + } if viewModel.isRefreshing { ProgressView().controlSize(.small) } diff --git a/macos-bar/Sources/CCSBarCheck/main.swift b/macos-bar/Sources/CCSBarCheck/main.swift index 0932ca85..a49e9b92 100644 --- a/macos-bar/Sources/CCSBarCheck/main.swift +++ b/macos-bar/Sources/CCSBarCheck/main.swift @@ -1436,6 +1436,15 @@ do { check(selectedIdx == 1, "screenPicker: exact hit selects right screen (index 1) for #1502 anchor") } +// MARK: BarVersionDisplay — pure display-string helper + +check(BarVersionDisplay.displayString(for: nil) == nil, "version nil raw -> nil (no dangling 'v')") +check(BarVersionDisplay.displayString(for: "") == nil, "version empty raw -> nil (no dangling 'v')") +check(BarVersionDisplay.displayString(for: "1.5.0") == "v1.5.0", "version '1.5.0' -> 'v1.5.0'") +check(BarVersionDisplay.displayString(for: "2.0.0") == "v2.0.0", "version '2.0.0' -> 'v2.0.0'") +// Outside a bundle, string() must not crash and returns nil (no assertion on value — bundle absent). +let _ = BarVersionDisplay.string() + // cleanup try? FileManager.default.removeItem(atPath: tmp) diff --git a/macos-bar/Sources/CCSBarCore/BarVersionDisplay.swift b/macos-bar/Sources/CCSBarCore/BarVersionDisplay.swift new file mode 100644 index 00000000..54bdee79 --- /dev/null +++ b/macos-bar/Sources/CCSBarCore/BarVersionDisplay.swift @@ -0,0 +1,28 @@ +import Foundation + +/// Pure helper for the version string shown in the bar panel header. +/// +/// Kept tiny so it can be tested in CCSBarCheck without a bundle present: +/// the display-string logic is pure-Foundation and has no AppKit dependency. +public enum BarVersionDisplay { + + /// Converts a raw version string to a "v{version}" display string. + /// + /// - Returns: `"v\(raw)"` when `raw` is non-nil and non-empty; `nil` otherwise. + /// + /// This is the logic under test. The actual `Bundle.main` lookup is in `string()`. + public static func displayString(for raw: String?) -> String? { + guard let v = raw, !v.isEmpty else { return nil } + return "v\(v)" + } + + /// Returns the display string for the running app's bundle version, or `nil` + /// when the key is absent (e.g. `swift run` outside a bundle). + /// + /// Never produces a dangling "v" prefix: if `CFBundleShortVersionString` is + /// missing or empty, returns `nil` so the caller can omit the label entirely. + public static func string() -> String? { + let raw = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String + return displayString(for: raw) + } +}