From af9a45358dad3685b8b77178e89dad382235d18d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 13:46:16 +0000 Subject: [PATCH 1/2] fix(version): bump csproj Version to 0.5.0 and use AppVersionInfo in UI test The in OpenClaw.Tray.WinUI.csproj was stale at 0.4.7 while the latest release tag is v0.5.0. In CI the version is overridden by GitVersion, but local builds and fallback paths displayed the wrong version. Also update A2UIDashboardScaleTest to assert AppVersionInfo.DisplayVersion instead of a hardcoded literal, so the test stays green across future bumps. Closes #570 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj | 2 +- tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj index 57a27d932..7284afb2d 100644 --- a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj +++ b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj @@ -9,7 +9,7 @@ true Assets\openclaw.ico OpenClawTray - 0.4.7 + 0.5.0 en-US x64;ARM64 win-x64;win-arm64 diff --git a/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs b/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs index bea35e8c2..7bd701c0f 100644 --- a/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs +++ b/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Shapes; +using OpenClaw.Shared; using static OpenClaw.Tray.UITests.A2UI; using static OpenClaw.Tray.UITests.TestSupport; @@ -320,7 +321,7 @@ private static string BuildDashboardJsonl() // ── Footer row ──────────────────────────────────────────────────── components.Add(Component("ftr", "Row", new() { ["children"] = Children("ftrVer", "ftrDiv", "ftrConn") })); - components.Add(Component("ftrVer", "Text", new() { ["text"] = Lit("v0.4.7"), ["usageHint"] = "caption" })); + components.Add(Component("ftrVer", "Text", new() { ["text"] = Lit(AppVersionInfo.DisplayVersion), ["usageHint"] = "caption" })); components.Add(Component("ftrDiv", "Divider", new() { ["axis"] = "vertical" })); components.Add(Component("ftrConn", "Text", new() { ["text"] = Lit("Connected"), ["usageHint"] = "caption" })); From c338a7bba4af4ba9f0dc66dbe55a599a589634cf Mon Sep 17 00:00:00 2001 From: Scott Hanselman Date: Thu, 28 May 2026 10:51:23 -0700 Subject: [PATCH 2/2] Prepare alpha fallback update testing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/RELEASING.md | 21 +++++++++ docs/VERSIONING.md | 24 +++++----- src/OpenClaw.Shared/AppVersionInfo.cs | 18 +++----- src/OpenClaw.Tray.WinUI/App.xaml.cs | 5 ++- .../OpenClaw.Tray.WinUI.csproj | 2 +- .../Services/UpdateChannelPolicy.cs | 40 +++++++++++++++++ .../AppVersionInfoTests.cs | 13 ++++++ .../OpenClaw.Tray.Tests.csproj | 1 + .../UpdateChannelPolicyTests.cs | 45 +++++++++++++++++++ 9 files changed, 143 insertions(+), 26 deletions(-) create mode 100644 src/OpenClaw.Tray.WinUI/Services/UpdateChannelPolicy.cs create mode 100644 tests/OpenClaw.Tray.Tests/UpdateChannelPolicyTests.cs diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 42e53d081..a0cebcf5b 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -27,6 +27,27 @@ git push origin vX.Y.Z-alpha.1 The stable MSIX package identity is `OpenClaw.Companion`. Alpha-tagged MSIX packages are patched during CI to use `OpenClaw.Companion.Alpha`, which lets testers install the signed alpha package without upgrading a stable MSIX install. +## Alpha update testing + +Normal update checks stay on stable releases. To test alpha release updates from a local Release build, isolate the run and opt into the alpha update channel before launching: + +```powershell +$env:OPENCLAW_TRAY_DATA_DIR = "$env:TEMP\OpenClawTray-alpha-test" +$env:OPENCLAW_UPDATE_CHANNEL = "alpha" +.\run-app-local.ps1 -Configuration Release -AllowNonMaster +``` + +`OPENCLAW_UPDATE_CHANNEL=alpha` enables pre-release checks and release-list fetching. The in-app updater downloads ZIP assets only; MSIX alpha packages are installed or sideloaded separately under the `OpenClaw.Companion.Alpha` identity. + +To force a local lower-version baseline against an already-published alpha, build the Release output with an explicit older version, then launch that output: + +```powershell +dotnet build .\src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj -c Release -r win-x64 -p:Version=0.5.0 +$env:OPENCLAW_TRAY_DATA_DIR = "$env:TEMP\OpenClawTray-alpha-test" +$env:OPENCLAW_UPDATE_CHANNEL = "alpha" +.\run-app-local.ps1 -Configuration Release -NoBuild -AllowNonMaster +``` + ## Why this is the correct flow - `GitVersion.yml` is configured for `ContinuousDelivery` with `tag-prefix: 'v'`. diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index de1ad8f03..ade6032a9 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -6,10 +6,10 @@ This project uses GitVersion for automatic semantic versioning based on git tags ### Version Properties in .csproj -The project file (`OpenClaw.Tray.WinUI.csproj`) defines only the `` property: +The project file (`OpenClaw.Tray.WinUI.csproj`) defines only the `` property. CI release builds override this value from the pushed tag; the checked-in value is the local-development fallback: ```xml -0.3.0 +0.6.0-alpha.1 ``` Other version-related properties (`FileVersion` and `AssemblyVersion`) are **not** explicitly set in the csproj files. This is intentional. @@ -17,9 +17,9 @@ Other version-related properties (`FileVersion` and `AssemblyVersion`) are **not ### Automatic Version Derivation When only `` is set in a .NET project: -- **AssemblyVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0` → `0.3.0.0`) -- **FileVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0` → `0.3.0.0`) -- **InformationalVersion**: Set to the full `Version` value including suffixes (e.g., `0.3.0-beta.1`) +- **AssemblyVersion**: Automatically set to the numeric part of `Version` (e.g., `0.6.0-alpha.1` -> `0.6.0.0`) +- **FileVersion**: Automatically set to the numeric part of `Version` (e.g., `0.6.0-alpha.1` -> `0.6.0.0`) +- **InformationalVersion**: Set to the full `Version` value including suffixes (e.g., `0.6.0-alpha.1`) This ensures all version properties stay in sync automatically. @@ -31,7 +31,7 @@ During CI builds (`.github/workflows/ci.yml`), GitVersion determines the semanti dotnet build -p:Version=${{ needs.test.outputs.semVer }} ``` -This `-p:Version=...` argument overrides the `` property in the csproj, and consequently also sets `FileVersion` and `AssemblyVersion` to match. +This `-p:Version=...` argument overrides the `` property in the csproj, and consequently also sets `FileVersion`, `AssemblyVersion`, and `InformationalVersion` from the release tag. ### Auto-Updater Version Detection @@ -41,7 +41,9 @@ The Updatum auto-updater determines the current application version by reading t Assembly.GetExecutingAssembly().GetName().Version ``` -This is why it's critical that `AssemblyVersion` (and `FileVersion`) match the semantic version - otherwise, the updater will get confused and keep offering the same update repeatedly. +This is why it's critical that `AssemblyVersion` (and `FileVersion`) match the numeric release version - otherwise, the updater will get confused and keep offering the same update repeatedly. Pre-release labels are preserved in `InformationalVersion` for display and diagnostics, but the updater's comparison is numeric. + +Stable update checks use GitHub's latest release endpoint and ignore pre-releases. Alpha testing is opt-in: set `OPENCLAW_UPDATE_CHANNEL=alpha` before launching a Release build. That enables Updatum pre-release checks and makes it fetch the release list instead of only the latest stable release. The updater is also constrained to `.zip` release assets, because the portable ZIP payloads are the assets intended for in-app replacement; MSIX packages are installed/sideloaded separately. ## Historical Issue @@ -79,8 +81,8 @@ By removing the hardcoded `FileVersion` and `AssemblyVersion` properties, they n `src/OpenClaw.Shared/AppVersionInfo.cs` exposes: -- `AppVersionInfo.Version` → bare string, e.g. `"0.4.7"` -- `AppVersionInfo.DisplayVersion` → `"v"` prefix, e.g. `"v0.4.7"` +- `AppVersionInfo.Version` -> bare string, e.g. `"0.6.0-alpha.1"` +- `AppVersionInfo.DisplayVersion` -> `"v"` prefix, e.g. `"v0.6.0-alpha.1"` It resolves the version by: @@ -88,8 +90,8 @@ It resolves the version by: still report the tray's version rather than the testhost / dotnet host). 2. Falling back to `Assembly.GetEntryAssembly()`, then to the Shared assembly. 3. Reading `AssemblyInformationalVersionAttribute` (preferred) or `AssemblyVersion`. -4. Stripping SourceLink build metadata (`+abc123`) **and** the SemVer pre-release suffix (`-beta.1`) so the - displayed value matches what Updatum compares (Updatum reads the numeric `AssemblyVersion` only). +4. Stripping SourceLink build metadata (`+abc123`) while preserving SemVer pre-release suffixes (`-alpha.1`) so + alpha builds are visibly identifiable in About, diagnostics, handshakes, and update-check status. For tests that need a deterministic value regardless of host process, set the `internal` test hook: diff --git a/src/OpenClaw.Shared/AppVersionInfo.cs b/src/OpenClaw.Shared/AppVersionInfo.cs index 3f292bc58..26d5b72f5 100644 --- a/src/OpenClaw.Shared/AppVersionInfo.cs +++ b/src/OpenClaw.Shared/AppVersionInfo.cs @@ -29,10 +29,10 @@ public static class AppVersionInfo /// internal static string? TestOverride { get; set; } - /// Bare version string, e.g. "0.4.7". + /// Bare version string, e.g. "0.6.0-alpha.1". public static string Version => TestOverride ?? _version; - /// Version prefixed with v, e.g. "v0.4.7". + /// Version prefixed with v, e.g. "v0.6.0-alpha.1". public static string DisplayVersion => "v" + Version; private static string ResolveVersion() @@ -48,7 +48,7 @@ private static string ResolveVersion() ?.InformationalVersion; if (!string.IsNullOrWhiteSpace(informational)) { - return NormalizeSemVer(informational); + return NormalizeInformationalVersion(informational); } var name = assembly.GetName().Version; @@ -82,20 +82,12 @@ private static string ResolveVersion() return null; } - private static string NormalizeSemVer(string s) + internal static string NormalizeInformationalVersion(string s) { - // Strip SourceLink build metadata, e.g. "0.4.7+abc123" -> "0.4.7". + // Strip SourceLink build metadata, e.g. "0.6.0-alpha.1+abc123" -> "0.6.0-alpha.1". var plus = s.IndexOf('+'); if (plus >= 0) s = s.Substring(0, plus); - // Strip the SemVer pre-release suffix, e.g. "0.4.7-beta.1" -> "0.4.7". - // This keeps the UI string aligned with Updatum, which compares the - // numeric AssemblyVersion only. Revisit if pre-release labels should - // ever be surfaced to users. - var dash = s.IndexOf('-'); - if (dash >= 0) s = s.Substring(0, dash); - return s; } } - diff --git a/src/OpenClaw.Tray.WinUI/App.xaml.cs b/src/OpenClaw.Tray.WinUI/App.xaml.cs index 57f0a6cd5..95e3c3e81 100644 --- a/src/OpenClaw.Tray.WinUI/App.xaml.cs +++ b/src/OpenClaw.Tray.WinUI/App.xaml.cs @@ -32,9 +32,12 @@ namespace OpenClawTray; public partial class App : Application, OpenClawTray.Services.IAppCommands { + internal static readonly UpdateChannelSettings UpdateChannel = UpdateChannelPolicy.Resolve(); internal static readonly UpdatumManager AppUpdater = new("shanselman", "openclaw-windows-hub") { - FetchOnlyLatestRelease = true, + FetchOnlyLatestRelease = UpdateChannel.FetchOnlyLatestRelease, + AllowPreReleases = UpdateChannel.AllowPreReleases, + AssetExtensionFilter = UpdateChannel.AssetExtensionFilter, InstallUpdateSingleFileExecutableName = "OpenClaw.Tray.WinUI", }; diff --git a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj index 48b8bb4b4..47e997402 100644 --- a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj +++ b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj @@ -9,7 +9,7 @@ true Assets\openclaw.ico OpenClawTray - 0.5.0 + 0.6.0-alpha.1 en-US x64;ARM64 win-x64;win-arm64 diff --git a/src/OpenClaw.Tray.WinUI/Services/UpdateChannelPolicy.cs b/src/OpenClaw.Tray.WinUI/Services/UpdateChannelPolicy.cs new file mode 100644 index 000000000..829574c5b --- /dev/null +++ b/src/OpenClaw.Tray.WinUI/Services/UpdateChannelPolicy.cs @@ -0,0 +1,40 @@ +using System; + +namespace OpenClawTray.Services; + +internal readonly record struct UpdateChannelSettings( + bool AllowPreReleases, + bool FetchOnlyLatestRelease, + string AssetExtensionFilter); + +internal static class UpdateChannelPolicy +{ + internal const string ChannelEnvironmentVariable = "OPENCLAW_UPDATE_CHANNEL"; + internal const string StableChannel = "stable"; + internal const string AlphaChannel = "alpha"; + internal const string PreReleaseChannel = "prerelease"; + internal const string ReleaseAssetExtension = "zip"; + + internal static UpdateChannelSettings Resolve(Func? envLookup = null) + { + envLookup ??= Environment.GetEnvironmentVariable; + var channel = envLookup(ChannelEnvironmentVariable); + var allowPreReleases = IsPreReleaseChannel(channel); + + return new UpdateChannelSettings( + AllowPreReleases: allowPreReleases, + FetchOnlyLatestRelease: !allowPreReleases, + AssetExtensionFilter: ReleaseAssetExtension); + } + + internal static bool IsPreReleaseChannel(string? channel) + { + if (string.IsNullOrWhiteSpace(channel)) + return false; + + var normalized = channel.Trim(); + return string.Equals(normalized, AlphaChannel, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalized, PreReleaseChannel, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalized, "pre-release", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs b/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs index 45b22e6eb..ed86fc5f6 100644 --- a/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs +++ b/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs @@ -67,10 +67,23 @@ public void Version_AfterClearingOverride_DoesNotReturnClearedValue() [Theory] [InlineData("1.2.3", "1.2.3")] [InlineData("0.4.7", "0.4.7")] + [InlineData("0.6.0-alpha.1", "0.6.0-alpha.1")] [InlineData("10.0.0", "10.0.0")] public void DisplayVersion_AlwaysEqualsVPlusVersion(string version, string expected) { AppVersionInfo.TestOverride = version; Assert.Equal("v" + expected, AppVersionInfo.DisplayVersion); } + + [Theory] + [InlineData("0.6.0", "0.6.0")] + [InlineData("0.6.0+abc123", "0.6.0")] + [InlineData("0.6.0-alpha.1", "0.6.0-alpha.1")] + [InlineData("0.6.0-alpha.1+abc123", "0.6.0-alpha.1")] + public void NormalizeInformationalVersion_StripsBuildMetadataButKeepsPreRelease( + string input, + string expected) + { + Assert.Equal(expected, AppVersionInfo.NormalizeInformationalVersion(input)); + } } diff --git a/tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj b/tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj index 4f6fdd2f2..757eb5b05 100644 --- a/tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj +++ b/tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj @@ -54,6 +54,7 @@ + diff --git a/tests/OpenClaw.Tray.Tests/UpdateChannelPolicyTests.cs b/tests/OpenClaw.Tray.Tests/UpdateChannelPolicyTests.cs new file mode 100644 index 000000000..32379817d --- /dev/null +++ b/tests/OpenClaw.Tray.Tests/UpdateChannelPolicyTests.cs @@ -0,0 +1,45 @@ +using OpenClawTray.Services; + +namespace OpenClaw.Tray.Tests; + +public sealed class UpdateChannelPolicyTests +{ + [Fact] + public void Resolve_DefaultsToStableLatestOnlyZipUpdates() + { + var settings = UpdateChannelPolicy.Resolve(_ => null); + + Assert.False(settings.AllowPreReleases); + Assert.True(settings.FetchOnlyLatestRelease); + Assert.Equal("zip", settings.AssetExtensionFilter); + } + + [Theory] + [InlineData("alpha")] + [InlineData("Alpha")] + [InlineData(" prerelease ")] + [InlineData("pre-release")] + public void Resolve_AlphaChannelAllowsPreReleasesAndFetchesReleaseList(string channel) + { + var settings = UpdateChannelPolicy.Resolve(key => + key == UpdateChannelPolicy.ChannelEnvironmentVariable ? channel : null); + + Assert.True(settings.AllowPreReleases); + Assert.False(settings.FetchOnlyLatestRelease); + Assert.Equal("zip", settings.AssetExtensionFilter); + } + + [Theory] + [InlineData("")] + [InlineData("stable")] + [InlineData("beta")] + public void Resolve_NonAlphaChannelStaysOnStableLatestOnly(string? channel) + { + var settings = UpdateChannelPolicy.Resolve(key => + key == UpdateChannelPolicy.ChannelEnvironmentVariable ? channel : null); + + Assert.False(settings.AllowPreReleases); + Assert.True(settings.FetchOnlyLatestRelease); + Assert.Equal("zip", settings.AssetExtensionFilter); + } +}