Fix what's-new re-appearing after a failed update (held for next release)

Replace the running-version-vs-saved-version trigger for the "what's new" popup with a
one-shot marker the updater writes ONLY on a successful update (UpdateApplier success
path). A failed/rolled-back update never writes it (and clears any stale one), so it can
no longer re-trigger what's-new — the old best-effort flag save could lose a race during
the update churn and leave the version mismatched, which was the bug.

New WhatsNewMarker seam (Write/Exists/Consume) + a SelfTest case for the consume-once
contract. MaybeShowWhatsNewAfterUpdate now shows iff the marker is present, then deletes
it; still records LastWhatsNewVersion for the import-offer's upgrade detection.

Not released yet — bundling with the connection-retry (#15) work in the next release.
No version bump, no manual change (internal fix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-06-23 17:16:54 +01:00
co-authored by Claude Opus 4.8
parent 36431d6d39
commit e710834458
5 changed files with 107 additions and 17 deletions
+46
View File
@@ -0,0 +1,46 @@
namespace RemSound.App;
/// <summary>
/// The one-shot "an update just succeeded" marker that drives the "what's new" popup. The in-app
/// updater (<see cref="UpdateApplier"/>) writes it into the install folder ONLY when a swap completes
/// successfully — never on a failed/rolled-back update — and MainForm consumes it (shows About once,
/// then deletes it) on the next launch. Because the signal is positive and written only on success, a
/// failed update can't re-trigger what's-new, which the old version-compare flag could.
///
/// Kept as a tiny seam (rather than inline file calls) so the consume contract is unit-testable: write
/// it into a throwaway folder, assert Exists, Consume once (true), then Exists is false and a second
/// Consume is false. See the SelfTest "what's-new marker" case.
/// </summary>
internal static class WhatsNewMarker
{
private static string PathFor(string baseDir) => Path.Combine(baseDir, RemSoundUpdater.WhatsNewMarkerName);
/// <summary>True if the success marker is present in <paramref name="baseDir"/> (the install folder).</summary>
public static bool Exists(string baseDir)
{
try { return File.Exists(PathFor(baseDir)); }
catch { return false; }
}
/// <summary>Write the marker into <paramref name="baseDir"/>. Called by the updater on success only.</summary>
public static void Write(string baseDir)
{
File.WriteAllText(PathFor(baseDir),
"One-shot marker: RemSound finished a successful update. \"What's new\" shows once on the next\r\n"
+ "launch, then this file is deleted. Safe to delete.\r\n");
}
/// <summary>Delete the marker if present, so what's-new shows exactly once. Returns true if a marker
/// was there (and is now gone); a delete is reliable in a way the old best-effort config-save wasn't.</summary>
public static bool Consume(string baseDir)
{
try
{
var path = PathFor(baseDir);
if (!File.Exists(path)) return false;
File.Delete(path);
return true;
}
catch { return false; }
}
}