diff --git a/SPEC.md b/SPEC.md
index ab9f6ac..8e2483d 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -779,7 +779,7 @@ Behaviour:
- **Diff viewer** selects the built-in viewer, a detected external program, or an executable chosen by the user.
- **Keyboard shortcut...** opens the shortcut recorder without changing the active shortcut unless Save succeeds.
- **Start at login** toggles per-user startup registration (section 24).
-- **About ClipDiff** opens a reusable native window showing the application icon, the product version and short source commit hash, Stuart Dunkeld, `stuartd.dev`, and the source repository link.
+- **About ClipDiff** opens a reusable native window showing the application icon, the last source commit (short hash) and build date/time in UTC (embedded at build time), without a product version, Stuart Dunkeld, `stuartd.dev`, and the source repository link.
- **Quit ClipDiff** removes the tray icon, unregisters native listeners/hotkeys, clears references to captured text, and shuts down.
- Double-clicking the tray icon may invoke **Show Diff** when available.
- The ordinary native right-click context-menu behaviour is sufficient.
diff --git a/src/ClipDiff.Windows/ClipDiff.Windows.csproj b/src/ClipDiff.Windows/ClipDiff.Windows.csproj
index 4825e28..5f19af1 100644
--- a/src/ClipDiff.Windows/ClipDiff.Windows.csproj
+++ b/src/ClipDiff.Windows/ClipDiff.Windows.csproj
@@ -11,6 +11,13 @@
app.manifest
Assets/ClipDiff.ico
+
+
+ $([System.DateTime]::UtcNow.ToString("O"))
+
+
+
+
diff --git a/src/ClipDiff.Windows/Views/AboutBuildInfoFormatter.cs b/src/ClipDiff.Windows/Views/AboutBuildInfoFormatter.cs
new file mode 100644
index 0000000..eea37e0
--- /dev/null
+++ b/src/ClipDiff.Windows/Views/AboutBuildInfoFormatter.cs
@@ -0,0 +1,29 @@
+using System.Globalization;
+
+namespace ClipDiff.Windows.Views;
+
+internal static class AboutBuildInfoFormatter
+{
+ private const int ShortCommitLength = 7;
+
+ public static string Format(string? informationalVersion, string? buildDateUtc)
+ {
+ var parts = informationalVersion?.Split('+', 2);
+ var commit = parts is { Length: 2 }
+ ? parts[1].Split('.').LastOrDefault(IsCommitHash)
+ : null;
+ var shortCommit = commit is null ? "Unknown" : commit[..ShortCommitLength];
+ var buildDate = DateTimeOffset.TryParse(buildDateUtc, CultureInfo.InvariantCulture,
+ DateTimeStyles.AssumeUniversal, out var timestamp)
+ ? timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm 'UTC'", CultureInfo.InvariantCulture)
+ : "Unknown";
+
+ return $"Last commit: {shortCommit}\nBuild date: {buildDate}";
+ }
+
+ private static bool IsCommitHash(string value) =>
+ value.Length >= ShortCommitLength && value.All(IsHexDigit);
+
+ private static bool IsHexDigit(char value) =>
+ value is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F';
+}
diff --git a/src/ClipDiff.Windows/Views/AboutVersionFormatter.cs b/src/ClipDiff.Windows/Views/AboutVersionFormatter.cs
deleted file mode 100644
index b3209d3..0000000
--- a/src/ClipDiff.Windows/Views/AboutVersionFormatter.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-namespace ClipDiff.Windows.Views;
-
-internal static class AboutVersionFormatter
-{
- private const int ShortCommitLength = 7;
-
- public static string Format(string? informationalVersion, Version? assemblyVersion)
- {
- var parts = informationalVersion?.Split('+', 2);
-
- var version = parts is { Length: > 0 } && !string.IsNullOrWhiteSpace(parts[0])
- ? parts[0]
- : assemblyVersion?.ToString(2) ?? "Unknown";
-
- if (parts is not { Length: 2 })
- {
- return version;
- }
-
- var commit = parts[1]
- .Split('.')
- .LastOrDefault(IsCommitHash);
-
- return commit is null
- ? version
- : $"{version} ({commit[..ShortCommitLength]})";
- }
-
- private static bool IsCommitHash(string value) =>
- value.Length >= ShortCommitLength && value.All(IsHexDigit);
-
- private static bool IsHexDigit(char value) =>
- value is >= '0' and <= '9' or >= 'a' and <= 'f' or >= 'A' and <= 'F';
-}
diff --git a/src/ClipDiff.Windows/Views/AboutWindow.xaml b/src/ClipDiff.Windows/Views/AboutWindow.xaml
index e2a28be..0befe77 100644
--- a/src/ClipDiff.Windows/Views/AboutWindow.xaml
+++ b/src/ClipDiff.Windows/Views/AboutWindow.xaml
@@ -55,9 +55,10 @@
-
+ Opacity="0.72"
+ TextWrapping="Wrap" />
diff --git a/src/ClipDiff.Windows/Views/AboutWindow.xaml.cs b/src/ClipDiff.Windows/Views/AboutWindow.xaml.cs
index 0f7c527..f186dfc 100644
--- a/src/ClipDiff.Windows/Views/AboutWindow.xaml.cs
+++ b/src/ClipDiff.Windows/Views/AboutWindow.xaml.cs
@@ -10,69 +10,72 @@ namespace ClipDiff.Windows.Views;
public partial class AboutWindow : Window
{
- public AboutWindow()
- {
- InitializeComponent();
- VersionText.Text = $"Version {GetDisplayVersion()}";
- }
+ public AboutWindow()
+ {
+ InitializeComponent();
+ BuildInfoText.Text = GetBuildInfo();
+ }
- public bool AllowClose { get; set; }
+ public bool AllowClose { get; set; }
- private void OnSourceInitialized(object? sender, EventArgs args)
- {
- var windowHandle = new WindowInteropHelper(this).Handle;
- var enabled = 1;
- var result = NativeMethods.DwmSetWindowAttribute(
- windowHandle,
- NativeMethods.DwmwaUseImmersiveDarkMode,
- ref enabled,
- sizeof(int));
+ private void OnSourceInitialized(object? sender, EventArgs args)
+ {
+ var windowHandle = new WindowInteropHelper(this).Handle;
+ var enabled = 1;
+ var result = NativeMethods.DwmSetWindowAttribute(
+ windowHandle,
+ NativeMethods.DwmwaUseImmersiveDarkMode,
+ ref enabled,
+ sizeof(int));
- if (result != 0)
- {
- NativeMethods.DwmSetWindowAttribute(
- windowHandle,
- NativeMethods.DwmwaUseImmersiveDarkModeBefore20H1,
- ref enabled,
- sizeof(int));
- }
- }
+ if (result != 0)
+ {
+ NativeMethods.DwmSetWindowAttribute(
+ windowHandle,
+ NativeMethods.DwmwaUseImmersiveDarkModeBefore20H1,
+ ref enabled,
+ sizeof(int));
+ }
+ }
- private static string GetDisplayVersion()
- {
- var assembly = typeof(AboutWindow).Assembly;
-
- var informationalVersion = assembly
- .GetCustomAttribute()?
- .InformationalVersion;
+ private static string GetBuildInfo()
+ {
+ var assembly = typeof(AboutWindow).Assembly;
- return AboutVersionFormatter.Format(informationalVersion, assembly.GetName().Version);
- }
+ var informationalVersion = assembly
+ .GetCustomAttribute()?
+ .InformationalVersion;
- private void OnLinkRequestNavigate(object sender, RequestNavigateEventArgs args)
- {
- try
- {
- Process.Start(new ProcessStartInfo(args.Uri.AbsoluteUri) { UseShellExecute = true });
- }
- catch (Exception exception) when (exception is Win32Exception or InvalidOperationException or NotSupportedException)
- {
- System.Media.SystemSounds.Beep.Play();
- }
+ var buildDate = assembly.GetCustomAttributes()
+ .FirstOrDefault(attribute => attribute.Key == "BuildDateUtc")?.Value;
- args.Handled = true;
- }
+ return AboutBuildInfoFormatter.Format(informationalVersion, buildDate);
+ }
- private void OnCloseClick(object sender, RoutedEventArgs args) => Hide();
+ private void OnLinkRequestNavigate(object sender, RequestNavigateEventArgs args)
+ {
+ try
+ {
+ Process.Start(new ProcessStartInfo(args.Uri.AbsoluteUri) { UseShellExecute = true });
+ }
+ catch (Exception exception) when (exception is Win32Exception or InvalidOperationException or NotSupportedException)
+ {
+ System.Media.SystemSounds.Beep.Play();
+ }
- private void OnClosing(object? sender, CancelEventArgs args)
- {
- if (AllowClose)
- {
- return;
- }
+ args.Handled = true;
+ }
- args.Cancel = true;
- Hide();
- }
+ private void OnCloseClick(object sender, RoutedEventArgs args) => Hide();
+
+ private void OnClosing(object? sender, CancelEventArgs args)
+ {
+ if (AllowClose)
+ {
+ return;
+ }
+
+ args.Cancel = true;
+ Hide();
+ }
}
diff --git a/tests/ClipDiff.Windows.Tests/AboutBuildInfoFormatterTests.cs b/tests/ClipDiff.Windows.Tests/AboutBuildInfoFormatterTests.cs
new file mode 100644
index 0000000..18f4161
--- /dev/null
+++ b/tests/ClipDiff.Windows.Tests/AboutBuildInfoFormatterTests.cs
@@ -0,0 +1,60 @@
+using ClipDiff.Windows.Views;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace ClipDiff.Windows.Tests;
+
+[TestClass]
+public sealed class AboutBuildInfoFormatterTests
+{
+ [TestMethod]
+ [DataRow("1.0+753aab75f36e959cf8dfa6d72a2175110c9ed608")]
+ [DataRow("2.3.4-preview.1+local.753aab75f36e959cf8dfa6d72a2175110c9ed608")]
+ public void ShowsCommitAndBuildDateWithoutVersion(string informationalVersion)
+ {
+ var result = AboutBuildInfoFormatter.Format(informationalVersion, "2026-10-19T10:43:44.0000000Z");
+
+ Assert.AreEqual("Last commit: 753aab7\nBuild date: 2026-10-19 10:43 UTC", result);
+ }
+
+ [TestMethod]
+ [DataRow(null)]
+ [DataRow("1.0")]
+ [DataRow("1.0+preview")]
+ public void MissingCommitIsExplicit(string? informationalVersion)
+ {
+ var result = AboutBuildInfoFormatter.Format(informationalVersion, "2026-10-19T10:43:44Z");
+
+ Assert.AreEqual("Last commit: Unknown\nBuild date: 2026-10-19 10:43 UTC", result);
+ }
+
+ [TestMethod]
+ [DataRow(null)]
+ [DataRow("")]
+ [DataRow("invalid")]
+ public void MissingOrInvalidBuildDateIsExplicit(string? buildDate)
+ {
+ var result = AboutBuildInfoFormatter.Format("1.0+753aab75f36e959cf8dfa6d72a2175110c9ed608", buildDate);
+
+ Assert.AreEqual("Last commit: 753aab7\nBuild date: Unknown", result);
+ }
+
+ [TestMethod]
+ public void NormalizesBuildDateToUtc()
+ {
+ var result = AboutBuildInfoFormatter.Format(null, "2026-10-19T11:43:44+01:00");
+
+ Assert.AreEqual("Last commit: Unknown\nBuild date: 2026-10-19 10:43 UTC", result);
+ }
+
+ [TestMethod]
+ public void BuildContainsTimestampMetadata()
+ {
+ var metadata = typeof(AboutBuildInfoFormatter).Assembly
+ .GetCustomAttributes(typeof(System.Reflection.AssemblyMetadataAttribute), false)
+ .Cast()
+ .Single(attribute => attribute.Key == "BuildDateUtc");
+
+ Assert.IsTrue(DateTimeOffset.TryParse(metadata.Value, System.Globalization.CultureInfo.InvariantCulture,
+ System.Globalization.DateTimeStyles.AssumeUniversal, out _));
+ }
+}
diff --git a/tests/ClipDiff.Windows.Tests/AboutVersionFormatterTests.cs b/tests/ClipDiff.Windows.Tests/AboutVersionFormatterTests.cs
deleted file mode 100644
index 5ac1b26..0000000
--- a/tests/ClipDiff.Windows.Tests/AboutVersionFormatterTests.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using ClipDiff.Windows.Views;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace ClipDiff.Windows.Tests;
-
-[TestClass]
-public sealed class AboutVersionFormatterTests
-{
- [TestMethod]
- public void IncludesShortCommitFromSdkInformationalVersion()
- {
- var result = AboutVersionFormatter.Format(
- "1.0+753aab75f36e959cf8dfa6d72a2175110c9ed608",
- new Version(1, 0, 0, 0));
-
- Assert.AreEqual("1.0 (753aab7)", result);
- }
-
- [TestMethod]
- public void FindsCommitAfterExistingBuildMetadata()
- {
- var result = AboutVersionFormatter.Format(
- "1.0+local.753aab75f36e959cf8dfa6d72a2175110c9ed608",
- new Version(1, 0, 0, 0));
-
- Assert.AreEqual("1.0 (753aab7)", result);
- }
-
- [TestMethod]
- public void OmitsNonCommitBuildMetadata()
- {
- var result = AboutVersionFormatter.Format("1.0+preview", new Version(1, 0, 0, 0));
-
- Assert.AreEqual("1.0", result);
- }
-
- [TestMethod]
- public void FallsBackToTwoPartAssemblyVersion()
- {
- var result = AboutVersionFormatter.Format(null, new Version(1, 0, 0, 0));
-
- Assert.AreEqual("1.0", result);
- }
-}