From 3438c3940eb0af9a680954526ba63274bf768c30 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 10 Jun 2026 15:25:45 -0400 Subject: [PATCH] feat(bar): show running app version in the panel header A right-aligned v{CFBundleShortVersionString} label fills the unused header space next to the CCS name, styled like the subtitle and hidden when no bundle version is available (e.g. swift run). The display logic lives in CCSBarCore as a pure helper with ccs-bar-check coverage, making the on-screen build identifiable after reinstalls. --- macos-bar/Sources/CCSBarApp/BarMenuView.swift | 5 ++++ macos-bar/Sources/CCSBarCheck/main.swift | 9 ++++++ .../CCSBarCore/BarVersionDisplay.swift | 28 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 macos-bar/Sources/CCSBarCore/BarVersionDisplay.swift 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) + } +}