Merge branch 'claude/android-release'
ci / build and test (push) Successful in 1m57s
ci / android head (push) Successful in 3m13s
ci / desktop nightly (push) Successful in 41s
ci / api image (push) Successful in 23s

This commit is contained in:
2026-08-05 22:49:51 +02:00
16 changed files with 712 additions and 24 deletions
@@ -61,6 +61,63 @@ public sealed class ClientPathsTests
}
}
/// <summary>
/// The nightly channel keeps its own profile, and everything else keeps the existing one.
/// </summary>
/// <remarks>
/// Both halves are load-bearing and they fail in opposite directions. A nightly sharing the release
/// build's directory would migrate the cache schema of a database the release build then opens — the
/// two are installed at once by design, so that is an ordinary Tuesday. And the release channel's
/// directory moving even slightly would orphan every existing install's cache, outbox and device key:
/// the application would start, find nothing, and ask for a server. See ADR 0013 decision 9.
/// </remarks>
[Theory]
[InlineData("release")]
[InlineData("")]
[InlineData(null)]
[InlineData("something nobody wrote")]
public void EveryChannelButTheNightlyKeepsTheExistingProfile(string? channel)
{
var resolved = ClientPaths.ForChannel(channel).DataDirectory;
// string.Equals with an explicit comparison rather than ShouldBe, here and below: these are paths
// built from the same constants, so the comparison that means anything is the exact one.
string.Equals(resolved, ClientPaths.Default.DataDirectory, StringComparison.Ordinal)
.ShouldBeTrue($"'{channel}' resolved to '{resolved}' rather than to the default profile");
}
[Fact]
public void TheNightlyProfileIsASiblingOfTheReleaseOne()
{
var release = ClientPaths.Default.DataDirectory;
var nightly = ClientPaths.ForChannel("nightly").DataDirectory;
string.Equals(nightly, release, StringComparison.Ordinal)
.ShouldBeFalse($"the nightly and the release build would share '{release}'");
// A sibling rather than a child, so neither install's uninstaller or reset can reach the other's.
string.Equals(
Path.GetDirectoryName(nightly),
Path.GetDirectoryName(release),
StringComparison.Ordinal)
.ShouldBeTrue($"'{nightly}' should sit beside '{release}'");
// And still identifiably ours, on either platform's spelling — the same property the default is
// checked for above, for the same reason.
nightly.Contains("dodossh", StringComparison.OrdinalIgnoreCase)
.ShouldBeTrue($"'{nightly}' should be identifiable as ours");
// The files follow the directory. A nightly writing the release build's cache or device key is the
// whole failure this separation exists to prevent, so it is asserted rather than inferred.
var separated = ClientPaths.ForChannel("nightly");
string.Equals(separated.CacheFile, ClientPaths.Default.CacheFile, StringComparison.Ordinal)
.ShouldBeFalse("the two builds would write one cache.db");
string.Equals(separated.DeviceKeyFile, ClientPaths.Default.DeviceKeyFile, StringComparison.Ordinal)
.ShouldBeFalse("the two builds would share one device key");
}
[Fact]
public void OnWindowsItIsTheLocalProfileAndNotTheRoamingOne()
{