From ed6f0588a31ea76b027724ec4127cedbe5c3bdbf Mon Sep 17 00:00:00 2001 From: Louis Qian Date: Mon, 30 Mar 2026 18:41:26 -0700 Subject: [PATCH 1/5] feat: make version clickable depending on type --- macos/Sources/Features/About/AboutView.swift | 35 ++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index d9a12e4dc..a7dfd7711 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -10,6 +10,30 @@ 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 { + /// Stable semver release (e.g. "1.3.1") with a link to release notes. + case stable(url: URL?) + /// Tip build: CI sets CFBundleShortVersionString to the raw short commit hash, + /// identical to GhosttyCommit. Show "Tip Release" text instead to avoid + /// duplicating the commit row. + case tip + /// Local dev build or unknown format. + case other(String) + } + + private var versionConfig: VersionConfig { + guard let version else { return .other("") } + if version.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil { + let slug = version.replacingOccurrences(of: ".", with: "-") + return .stable(url: docsURL?.appendingPathComponent("install/release-notes/\(slug)")) + } + if version.range(of: #"^[0-9a-f]{7,40}$"#, options: .regularExpression) != nil { + return .tip + } + return .other(version) + } + private var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String } #if os(macOS) @@ -60,8 +84,15 @@ struct AboutView: View { .textSelection(.enabled) VStack(spacing: 2) { - if let version { - PropertyRow(label: "Version", text: version) + switch versionConfig { + case .stable(let url): + PropertyRow(label: "Version", text: version ?? "", url: url) + case .tip: + PropertyRow(label: "Version", text: "Tip Release") + case .other(let v) where !v.isEmpty: + PropertyRow(label: "Version", text: v) + default: + EmptyView() } if let build { PropertyRow(label: "Build", text: build) From b29f261dc89b6c9ed1b37d700ec3f815dcf00462 Mon Sep 17 00:00:00 2001 From: Louis Qian Date: Mon, 30 Mar 2026 18:44:49 -0700 Subject: [PATCH 2/5] chore: clean up versionConfig to be init-able --- macos/Sources/Features/About/AboutView.swift | 26 +++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index a7dfd7711..49958d9fa 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -20,19 +20,23 @@ struct AboutView: View { case tip /// Local dev build or unknown format. case other(String) + + init(version: String?, docsURL: URL?) { + guard let version else { self = .other(""); return } + if version.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil { + let slug = version.replacingOccurrences(of: ".", with: "-") + self = .stable(url: docsURL?.appendingPathComponent("install/release-notes/\(slug)")) + return + } + if version.range(of: #"^[0-9a-f]{7,40}$"#, options: .regularExpression) != nil { + self = .tip + return + } + self = .other(version) + } } - private var versionConfig: VersionConfig { - guard let version else { return .other("") } - if version.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil { - let slug = version.replacingOccurrences(of: ".", with: "-") - return .stable(url: docsURL?.appendingPathComponent("install/release-notes/\(slug)")) - } - if version.range(of: #"^[0-9a-f]{7,40}$"#, options: .regularExpression) != nil { - return .tip - } - return .other(version) - } + private var versionConfig: VersionConfig { VersionConfig(version: version, docsURL: docsURL) } private var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String } From 90d71dd2f62b33ddb44ba8cb647e24b3eb76e131 Mon Sep 17 00:00:00 2001 From: Louis Qian Date: Mon, 30 Mar 2026 18:49:17 -0700 Subject: [PATCH 3/5] chore: clean up comments --- macos/Sources/Features/About/AboutView.swift | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index 49958d9fa..b9c0b6098 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -12,20 +12,15 @@ struct AboutView: View { private var version: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String } private enum VersionConfig { - /// Stable semver release (e.g. "1.3.1") with a link to release notes. - case stable(url: URL?) - /// Tip build: CI sets CFBundleShortVersionString to the raw short commit hash, - /// identical to GhosttyCommit. Show "Tip Release" text instead to avoid - /// duplicating the commit row. + case stable(version: String, url: URL?) case tip - /// Local dev build or unknown format. case other(String) init(version: String?, docsURL: URL?) { guard let version else { self = .other(""); return } if version.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil { let slug = version.replacingOccurrences(of: ".", with: "-") - self = .stable(url: docsURL?.appendingPathComponent("install/release-notes/\(slug)")) + self = .stable(version: version, url: docsURL?.appendingPathComponent("install/release-notes/\(slug)")) return } if version.range(of: #"^[0-9a-f]{7,40}$"#, options: .regularExpression) != nil { @@ -89,8 +84,8 @@ struct AboutView: View { VStack(spacing: 2) { switch versionConfig { - case .stable(let url): - PropertyRow(label: "Version", text: version ?? "", url: url) + case .stable(let version, let url): + PropertyRow(label: "Version", text: version, url: url) case .tip: PropertyRow(label: "Version", text: "Tip Release") case .other(let v) where !v.isEmpty: From 183e2cef2f17c6b43427635ac124edc13cbd1425 Mon Sep 17 00:00:00 2001 From: Louis Qian Date: Mon, 30 Mar 2026 18:51:45 -0700 Subject: [PATCH 4/5] chore: clean up switch statement --- macos/Sources/Features/About/AboutView.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index b9c0b6098..6545e644a 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -15,9 +15,10 @@ struct AboutView: View { case stable(version: String, url: URL?) case tip case other(String) + case none init(version: String?, docsURL: URL?) { - guard let version else { self = .other(""); return } + guard let version else { self = .none; return } if version.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil { let slug = version.replacingOccurrences(of: ".", with: "-") self = .stable(version: version, url: docsURL?.appendingPathComponent("install/release-notes/\(slug)")) @@ -88,9 +89,9 @@ struct AboutView: View { PropertyRow(label: "Version", text: version, url: url) case .tip: PropertyRow(label: "Version", text: "Tip Release") - case .other(let v) where !v.isEmpty: + case .other(let v): PropertyRow(label: "Version", text: v) - default: + case .none: EmptyView() } if let build { From 010880a90ae5988335a6174493a6f2d2644be08b Mon Sep 17 00:00:00 2001 From: Louis Qian Date: Mon, 30 Mar 2026 20:15:01 -0700 Subject: [PATCH 5/5] chore: make url computed property & rework enum signature --- macos/Sources/Features/About/AboutView.swift | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index 6545e644a..af6f64504 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -12,27 +12,36 @@ struct AboutView: View { private var version: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String } private enum VersionConfig { - case stable(version: String, url: URL?) - case tip + case stable(version: String) + case tip(commit: String?) case other(String) case none - init(version: String?, docsURL: URL?) { + init(version: String?) { guard let version else { self = .none; return } if version.range(of: #"^\d+\.\d+\.\d+$"#, options: .regularExpression) != nil { - let slug = version.replacingOccurrences(of: ".", with: "-") - self = .stable(version: version, url: docsURL?.appendingPathComponent("install/release-notes/\(slug)")) + self = .stable(version: version) return } if version.range(of: #"^[0-9a-f]{7,40}$"#, options: .regularExpression) != nil { - self = .tip + 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, docsURL: docsURL) } + private var versionConfig: VersionConfig { VersionConfig(version: version) } private var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String } @@ -85,8 +94,8 @@ struct AboutView: View { VStack(spacing: 2) { switch versionConfig { - case .stable(let version, let url): - PropertyRow(label: "Version", text: version, url: url) + case .stable(let version): + PropertyRow(label: "Version", text: version, url: versionConfig.url) case .tip: PropertyRow(label: "Version", text: "Tip Release") case .other(let v):