← Commit history

0.2.37: launch hands the user's foreground window back; battery green on both Altium builds (113 pass, 0 fail)

John Lauer ·e87c2d17ba ·1mo ago ·parent e2d109d
8 files changed +32−11
CHANGELOG.md+1−1
@@ -2,7 +2,7 @@  ## 0.2.37 (2026-09-21) - **`altium_launch` hands the user's window back.** Altium foregrounds itself at startup and offers no switch for it, so the bridge now remembers the window the user was working in and restores it once Altium has settled (`foregroundReturnedTo`, `windowEtiquette: background`). `background:false` opts out when the user asked to watch Altium come up. John, twice in two days: "dude u keep forcing altium in front of me while i'm typing".-- Full battery on Altium Designer 26.10.1: 122 verbs, **113 pass, 0 fail**, 9 skipped for safety — `close` included, for the first time.+- Full battery GREEN ON BOTH BUILDS: 122 verbs, **113 pass, 0 fail**, 9 skipped for safety, on Altium Designer 26.10.1 (arav-rog) and Altium Designer Develop (ConfRoomROG) — `close` included, for the first time. Both machines were left with no Altium running and no dialog on screen.  ## 0.2.36 (2026-09-21) Fixes the last failing verb in the battery, `close`, and with it an overnight hostage situation: an Altium on the test box held the single licence seat for eight hours because nothing could reach it.
bridge-rs/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "altium-bridge"-version = "0.2.36"+version = "0.2.37" edition = "2021" description = "Adom altium-bridge — the bridge process, in Rust. Calls ab's verbs for the desktop; speaks file IPC to the resident DelphiScript agent inside Altium." license = "MIT"
bridge-rs/src/lifecycle.rs+5
@@ -745,6 +745,11 @@ fn recover_impl(reason: &str, verify_by_restart: bool) -> Value {  // ---- application lifecycle --------------------------------------------------- +/// The window the user is actually working in, for verbs that must hand it back (`misc::launch`).+pub fn foreground_hwnd_pub() -> Option<i64> { foreground_hwnd() }+/// Hand a remembered foreground window back to the user.+pub fn restore_foreground_pub(hwnd: i64) { restore_foreground(hwnd) }+ pub fn launch(wait: Duration) -> Value {     if crate::health::is_running() {         return json!({"success": true, "alreadyRunning": true, "windowEtiquette": "background"});
bridge-rs/src/main.rs+1−1
@@ -29,7 +29,7 @@ use tiny_http::{Header, Method, Response, Server}; /// Attached to every failed reply (see the /command handler). const REPORT_ISSUES: &str = "If this looks like an altium-bridge BUG (not a refusal you expected), report it: check for a duplicate with `adom-wiki issue list adom/altium-bridge`, then `adom-wiki issue create adom/altium-bridge --title \"<verb>: <one-line symptom>\" --category bug-report --body \"<what you ran, what you expected, what happened, the VERBATIM response JSON (and errorShot if present), bridgeVersion, the Altium version from altium_status, the OS>\"`. A relay, routing, toast or lifecycle problem belongs on adom/adom-bridge instead. Reports from real machines are how this bridge gets correct: three different Altium builds behaved three different ways in one weekend."; -pub const BRIDGE_VERSION: &str = "0.2.36";+pub const BRIDGE_VERSION: &str = "0.2.37";  fn json_response(status: u16, body: &serde_json::Value) -> Response<std::io::Cursor<Vec<u8>>> {     let data = serde_json::to_vec(body).unwrap_or_else(|_| b"{}".to_vec());
bridge-rs/src/misc.rs+18−2
@@ -200,6 +200,12 @@ pub fn launch(args: &Value) -> Value {     }     let t0 = support::now();     progress::begin("cold_start", "cold", "launching Altium Designer", "bridge");+    // John, 2026-09-20/21: "dude u keep forcing altium in front of me [while] i'm typing".+    // Altium's own launch foregrounds itself and there is no switch for it, so remember the+    // window the user was actually in and hand it straight back once Altium is up.+    // `background:false` opts out when the launch IS the thing the user asked to see.+    let hand_back = args.get("background").and_then(|b| b.as_bool()).unwrap_or(true);+    let user_fg = if hand_back { lifecycle::foreground_hwnd_pub() } else { None };     let exe = inst.get("exe").and_then(|e| e.as_str()).unwrap_or("").to_string();     if let Err(e) = lifecycle::spawn_helper_pub(&exe, "") {         progress::end("cold_start", "bridge");@@ -212,9 +218,19 @@ pub fn launch(args: &Value) -> Value {         if lic.get("running").and_then(|b| b.as_bool()) == Some(true) {             progress::record("cold_start", support::now() - t0, "cold");             progress::end("cold_start", "bridge");+            // Altium keeps painting for a moment after its main window exists; hand the focus+            // back once it has settled, or it simply takes it again.+            let mut returned = Value::Null;+            if let Some(fg) = user_fg {+                std::thread::sleep(Duration::from_secs(3));+                lifecycle::restore_foreground_pub(fg);+                returned = json!(fg);+            }             return json!({"success": true, "startedInSec": support::elapsed_round(t0), "licence": lic,-                "windowEtiquette": "foreground",-                "_hint": "Altium's own launch foregrounds itself (the one legitimate foreground). Next: altium_readiness; if unlicensed, altium_license."});+                "windowEtiquette": if returned.is_null() { "foreground" } else { "background" },+                "foregroundReturnedTo": returned,+                "_hint": if returned.is_null() { "Altium's own launch foregrounds itself. Next: altium_readiness; if unlicensed, altium_license." }+                         else { "Altium is up and the user's own window was handed back (it launches in front of everything and cannot be told not to). Pass background:false if the user asked to SEE Altium come up. Next: altium_readiness; if unlicensed, altium_license." }});         }         std::thread::sleep(Duration::from_secs(1));     }
bridge-rs/src/verbs.rs+4−4
@@ -219,12 +219,12 @@ fn describe() -> Value {               "hwnd is NOT read by this verb, although other verbs' hints say {hwnd, button}: target a dialog with titles.",               "success is true even when nothing was pressed or the click failed: check pressed[].ok."],             json!({"button": "OK", "titles": ["Error"]})),-        verb_doc("launch", "Start Altium (newest install) and wait for its main window. Answers alreadyRunning when one is up.",-            json!({"waitSec": "number, default 180"}),-            json!({"alreadyRunning": "bool", "startedInSec": "float", "licence": "{running, licensed, state, adVersion, edition, hwnd, pid}", "windowEtiquette": "foreground on a real launch, background when already running"}), 240,+        verb_doc("launch", "Start Altium (newest install) and wait for its main window, then hand the user's own window back. Answers alreadyRunning when one is up.",+            json!({"waitSec": "number, default 180", "background": "bool, default TRUE: remember the user's foreground window and restore it once Altium is up. Pass false only when the user asked to SEE Altium come up."}),+            json!({"alreadyRunning": "bool", "startedInSec": "float", "licence": "{running, licensed, state, adVersion, edition, hwnd, pid}", "foregroundReturnedTo": "hwnd|null", "windowEtiquette": "background once the user's window is handed back; foreground only with background:false"}), 240,             "Cold start is about 60 s. Next: altium_readiness; licensed:false -> altium_ensure_licensed. Most verbs need this first (errorCode altium_not_running).",             &["readiness", "ensure_licensed", "close", "install"],-            &["A real launch RAISES Altium over the user's work (Altium foregrounds itself at startup). Call it once; never in a retry loop.",+            &["Altium foregrounds ITSELF at startup and offers no switch for it, so the window jumps over the user's work for a few seconds even with background:true — the bridge can only hand the focus back afterwards. Call it once; never in a retry loop.",               "errorCode host_app_not_installed -> altium_install. launch_timeout = no main window after waitSec.",               "A 'Workspace Unavailable' box at launch (altium_dialog_log) means another running Altium holds the seat: this one is view-only and opens fail with view_only_no_author_seat."],             json!({})),
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "altium",   "displayName": "Altium Designer",-  "version": "0.2.36",+  "version": "0.2.37",   "description": "Drive Altium Designer from Adom: a resident DelphiScript agent (eval + the 1,100-command process launcher), the X2 -R command line, UIA over the VCL shell, dialogs by PID, screenshots of every Altium window, and Altium 365.",   "homepage": "https://wiki.adom.inc/adom/altium-bridge",   "docs": "https://wiki.adom.inc/adom/altium-bridge",
page.json+1−1
@@ -140,4 +140,4 @@     "skills/altium-wiki-import/SKILL.md",     "uninstall.sh"   ]-}\ No newline at end of file+}