From de98ea694fe16a5e673282c86acc1fecfa8b4cd1 Mon Sep 17 00:00:00 2001
From: Alexa <53471801+Alexa-RR@users.noreply.github.com>
Date: Wed, 27 May 2026 12:05:21 +0300
Subject: [PATCH] launcher: side-by-side versions.json fallback + empty-state
UX
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The version dropdown sat silently empty when the GitHub manifest
fetch 404'd (private repo, network down, etc.) and there was no
DORKNET_LOCAL_MANIFEST to fall back to. Two fixes:
- csproj copies the repo-root versions.json next to dorknet.exe on
build, and LocalOverrides.GetLocalManifestPath checks that path
as a fallback after the env var. Dev checkouts against a private
repo now Just Work — no env vars needed.
- When the manifest can't be loaded, RefreshManifestAsync now
inserts a disabled placeholder "(no versions - check internet or
set DORKNET_LOCAL_MANIFEST)" in the dropdown so the user sees the
problem at a glance instead of a silent blank.
- LocalOverrides.DescribeManifestSource shows in Settings whether
the active manifest came from the env var, the side-by-side
fallback, or the live GitHub fetch.
---
launcher/Backend/LocalOverrides.cs | 36 +++++++++++++++++++++++++-----
launcher/DorkNet.Launcher.csproj | 13 +++++++++++
launcher/MainWindow.xaml.cs | 18 ++++++++++++---
3 files changed, 59 insertions(+), 8 deletions(-)
diff --git a/launcher/Backend/LocalOverrides.cs b/launcher/Backend/LocalOverrides.cs
index b627557..6775714 100644
--- a/launcher/Backend/LocalOverrides.cs
+++ b/launcher/Backend/LocalOverrides.cs
@@ -26,14 +26,21 @@ public static class LocalOverrides
public const string ManifestEnvVar = "DORKNET_LOCAL_MANIFEST";
public const string ReleasesEnvVar = "DORKNET_LOCAL_RELEASES";
- /// Returns the configured manifest path if the env var is
- /// set AND the file exists; null otherwise. Caller decides whether
- /// to fall back to the network.
+ /// Returns a local manifest path to use instead of the
+ /// GitHub fetch. Prefers DORKNET_LOCAL_MANIFEST when set; if
+ /// that's unset, falls back to a side-by-side versions.json
+ /// next to dorknet.exe (so dev checkouts against a private
+ /// repo work without env-var wiring — the launcher's csproj copies
+ /// the repo-root versions.json to its bin output on build).
public static string? GetLocalManifestPath()
{
var path = Environment.GetEnvironmentVariable(ManifestEnvVar);
- if (string.IsNullOrEmpty(path)) return null;
- return File.Exists(path) ? path : null;
+ if (!string.IsNullOrEmpty(path) && File.Exists(path)) return path;
+
+ var sideBySide = Path.Combine(AppContext.BaseDirectory, "versions.json");
+ if (File.Exists(sideBySide)) return sideBySide;
+
+ return null;
}
/// Returns the configured releases-root if the env var is
@@ -87,4 +94,23 @@ public static class LocalOverrides
? $"{envVar} -> {raw} [active]"
: $"{envVar} -> {raw} [SET BUT NOT FOUND on disk]");
}
+
+ /// Best-effort description of where the manifest is
+ /// actually coming from this session. Mainly for the Settings UI
+ /// so the user can tell at a glance "side-by-side dev manifest"
+ /// vs "live GitHub fetch" vs "broken env-var path".
+ public static string DescribeManifestSource()
+ {
+ var env = Environment.GetEnvironmentVariable(ManifestEnvVar);
+ if (!string.IsNullOrEmpty(env))
+ {
+ return File.Exists(env)
+ ? $"manifest source: {env} (env var)"
+ : $"manifest source: {env} (env var, NOT FOUND — falling back)";
+ }
+ var sideBySide = Path.Combine(AppContext.BaseDirectory, "versions.json");
+ if (File.Exists(sideBySide))
+ return $"manifest source: {sideBySide} (side-by-side fallback)";
+ return "manifest source: live GitHub fetch (no local override)";
+ }
}
diff --git a/launcher/DorkNet.Launcher.csproj b/launcher/DorkNet.Launcher.csproj
index 2eaf223..5b09e99 100644
--- a/launcher/DorkNet.Launcher.csproj
+++ b/launcher/DorkNet.Launcher.csproj
@@ -27,4 +27,17 @@
+
+
+
+ versions.json
+ PreserveNewest
+
+
+
diff --git a/launcher/MainWindow.xaml.cs b/launcher/MainWindow.xaml.cs
index 9b245c2..b081528 100644
--- a/launcher/MainWindow.xaml.cs
+++ b/launcher/MainWindow.xaml.cs
@@ -184,13 +184,25 @@ public partial class MainWindow : Window
private async Task RefreshManifestAsync()
{
_manifest = await new VersionsManifest().FetchAsync();
+ VersionSelect.Items.Clear();
if (_manifest is null)
{
- HostStatusText.Text = "Couldn't load versions.json from GitHub. " +
- "Check internet, then reopen.";
+ // Empty placeholder so the user sees *something* in the
+ // dropdown instead of a silent blank — helps when the public
+ // repo is unreachable (e.g. private repo + no env override).
+ var placeholder = new ComboBoxItem
+ {
+ Content = "(no versions — check internet or set DORKNET_LOCAL_MANIFEST)",
+ IsEnabled = false,
+ Tag = null,
+ };
+ VersionSelect.Items.Add(placeholder);
+ placeholder.IsSelected = true;
+ HostStatusText.Text = "Couldn't load versions.json. " +
+ "Either the repo isn't reachable, or DORKNET_LOCAL_MANIFEST " +
+ "points at a missing/invalid file — check Settings.";
return;
}
- VersionSelect.Items.Clear();
foreach (var v in _manifest.Branches.Where(b => b.Supported))
{
var item = new ComboBoxItem