From 53bba10b3ef5177e356e73835473b1ca5803fc5f Mon Sep 17 00:00:00 2001 From: Kelorastus <108483685+Kelorastus@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:10:07 +0200 Subject: [PATCH] dev mode: prefer the DHP's data, fall back to the local install A dev run is for working on the client, and the data worth working against lives on the DHP. Deciding this by "is anything installed" guessed wrong both ways: a PC with an old install silently ran on stale local data, with nothing on screen saying so, while a PC with none was refused outright. Now: dev mode + the DHP answering -> the client runs from the checkout against the DHP's API and tiles, installed or not. No DHP -> the local stack, as before. Co-Authored-By: Claude Opus 5 --- src-tauri/src/launcher.rs | 36 ++++++++++++++++++++++++++----- src/components/ActionPanel.svelte | 9 +++++--- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/launcher.rs b/src-tauri/src/launcher.rs index 1c869ac..62300a5 100644 --- a/src-tauri/src/launcher.rs +++ b/src-tauri/src/launcher.rs @@ -696,22 +696,27 @@ impl Launcher { repo.join("tools").join("build").join("build.bat").is_file().then_some(repo) } - /// Can this PC run a dev client with no install at all, straight against its DHP? + /// The DHP-backed dev run: the client comes from the checkout and its data comes from the + /// DHP - live tracks, live tiles, nothing installed needed. /// - /// Only with nothing installed: where there IS an install the local stack is the better - /// run - it holds this PC's own data and it is what an operator actually uses. This is - /// purely the case that used to be refused outright. + /// PREFERRED whenever dev mode is on and the DHP answers, installed or not. A dev run is + /// for working on the client, and the DHP holds the data worth working against; the local + /// stack is the fallback for when the DHP cannot be reached. Deciding it by "is anything + /// installed" guessed wrong in both directions: a PC with an old install silently ran on + /// stale local data, and nothing on screen said so. /// /// Every reason it might not work is checked here rather than at launch, because the hub /// needs to know whether to offer Launch at all. fn dhp_direct(inner: &Inner) -> Option { - if Self::dev_engine(inner).is_none() || !inner.online || inner.state.installed_version.is_some() { + if Self::dev_engine(inner).is_none() || !inner.online { return None; } let mcp = inner.settings.mcp_url.trim(); if mcp.is_empty() { return None; } + // The DHP's own Engine API, as its health answer named it. None means the DHP did not + // say where it is, so there is nothing to point a client at and the run falls back. Some(DhpDirect { engine_url: inner.engine_url.clone()?, mcp_url: mcp.to_string(), @@ -2975,6 +2980,27 @@ fn swapped_user<'a>(asked: &'a str, operator: Option<&str>) -> Option<&'a str> { mod tests { use super::*; + /// The rule the hub now follows: the DHP is PREFERRED, the install is the fallback. + /// Written as a table because the old version decided this by "is anything installed", + /// which was wrong in both directions. + #[test] + fn the_dhp_is_preferred_and_the_install_is_the_fallback() { + // (dev mode, online, installed) -> what a dev run should use + let cases = [ + (true, true, true, "dhp"), // installed, but online: the live data wins + (true, true, false, "dhp"), // nothing installed: the case that was refused outright + (true, false, true, "local"), // no DHP: fall back to what is on this PC + (true, false, false, "none"), // no DHP and nothing installed: nothing to run + (false, true, true, "local"), // dev mode off: an ordinary operator launch + ]; + for (dev, online, installed, want) in cases { + // dhp_direct() needs an Inner, so this asserts the DECISION the gate encodes rather + // than calling it: dev && online picks the DHP, and only then does install matter. + let got = if dev && online { "dhp" } else if installed { "local" } else { "none" }; + assert_eq!(got, want, "dev={dev} online={online} installed={installed}"); + } + } + #[test] fn nothing_installed_still_launches_when_the_dhp_can_stand_in() { // The whole point of the DHP-direct run: a checkout and a reachable DHP are enough. diff --git a/src/components/ActionPanel.svelte b/src/components/ActionPanel.svelte index 063edc1..7a75d9e 100644 --- a/src/components/ActionPanel.svelte +++ b/src/components/ActionPanel.svelte @@ -243,9 +243,12 @@

{#if checking} Checking {site}… - {:else if !s?.installed} - Nothing is installed here: the client comes from your checkout and runs on - {site}'s data. You sign in inside ODIAN, not here. + {:else if s?.dhp_direct} + + Live from {site}: the client comes from your checkout and its data, + tracks and map come from the DHP. You sign in inside ODIAN, not here. + {#if s?.installed}Nothing local starts; {s.installed} is used only when the DHP is unreachable.{/if} {:else if s?.update_required} ODIAN {s.installed} is installed, {s.available} is on {site}. A dev run starts