macOS: Make version in about dialog clickable (#12007)

- Fixes: https://github.com/ghostty-org/ghostty/issues/11964

Made a private enum type `VersionConfig` to reference whether the
release is a semver or tip, makes it easier for later in the view to
`switch` between cases.

I do think there could be a better place for this enum or we can get rid
of it, open to opinions. Right now version parsing is kind of duplicated
between `AboutView` and `UpdateModalView` so we can also extract to a
common helper if wanted.

Tested by manually setting `Marketing Version` in build settings to 

`1.3.1`
<img width="412" height="532" alt="Screenshot 2026-03-30 at 18 31 15"
src="https://github.com/user-attachments/assets/285bb94d-138b-4169-bb66-684eb04b6ca3"
/>

`332b2aefc`
<img width="412" height="532" alt="Screenshot 2026-03-30 at 18 32 48"
src="https://github.com/user-attachments/assets/fea30d39-bea7-4885-8221-1696e148f45e"
/>

### AI Disclosure
I used Sonnet 4.6 to understand where the version strings came from and
in what format, it read release yml files to see what's going on. Then
it proposed really bad code so I manually went in and cleaned up the
view.
pull/12021/head
Mitchell Hashimoto 2026-03-31 06:38:59 -07:00 committed by GitHub
commit 292bf13d06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 42 additions and 2 deletions

View File

@ -10,6 +10,39 @@ struct AboutView: View {
private var build: String? { Bundle.main.infoDictionary?["CFBundleVersion"] as? String }
private var commit: String? { Bundle.main.infoDictionary?["GhosttyCommit"] as? String }
private var version: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String }
private enum VersionConfig {
case stable(version: String)
case tip(commit: String?)
case other(String)
case none
init(version: String?) {
guard let version else { self = .none; return }
if version.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil {
self = .stable(version: version)
return
}
if version.range(of: #"^[0-9a-f]{7,40}$"#, options: .regularExpression) != nil {
self = .tip(commit: version)
return
}
self = .other(version)
}
var url: URL? {
switch self {
case .stable(let version):
let slug = version.replacingOccurrences(of: ".", with: "-")
return URL(string: "https://ghostty.org/docs/install/release-notes/\(slug)")
default:
return nil
}
}
}
private var versionConfig: VersionConfig { VersionConfig(version: version) }
private var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String }
#if os(macOS)
@ -60,8 +93,15 @@ struct AboutView: View {
.textSelection(.enabled)
VStack(spacing: 2) {
if let version {
PropertyRow(label: "Version", text: version)
switch versionConfig {
case .stable(let version):
PropertyRow(label: "Version", text: version, url: versionConfig.url)
case .tip:
PropertyRow(label: "Version", text: "Tip Release")
case .other(let v):
PropertyRow(label: "Version", text: v)
case .none:
EmptyView()
}
if let build {
PropertyRow(label: "Build", text: build)