← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.13.22\ No newline at end of file+1.13.23\ No newline at end of file
CHANGELOG.md+15−1
@@ -3,7 +3,21 @@ All notable changes to the Adom Bridge Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/fusion-bridge` -## 1.13.22 (unreleased)+## 1.13.23 (2026-10-30)++- The standard's app tier in Fusion's terms:+  - `fusion_diagnostics`: facts for a bug report without driving Fusion (bridge version and folders, bundled add-in,+    Fusion launcher, processes and windows, whether the add-in answers, the Adom/Fusion/Autodesk environment with the+    values of credential-named variables never shown);+  - `fusion_instances`: every running Fusion process;+  - `fusion_progress`: the long operation in flight, measured phase and verb timings, recent runs;+  - `fusion_foreground {reason}`: Fusion brought in front, the sanctioned exception: the user is notified why and given+    three seconds first.+- panel and selection answer not_supported: Fusion's editors show their own panels (the bridge opens surfaces with+  fusion_show_*), and Fusion does not show the user's board selection to the bridge (a ULP's ingroup() reads no part+  even right after a GROUP, checked live). 345 verbs.++## 1.13.22 (2026-10-30)  - `fusion_variants` (the standard's variants): a design's assembly variants, each with its not-fitted parts and   alternate values, read from the schematic. Making, changing or removing one is refused with not_supported: Fusion
bridge.json+9−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.13.22",+  "version": "1.13.23",   "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.",   "homepage": "https://wiki.adom.inc/adom/fusion-bridge",   "author": "Adom Inc.",@@ -99,6 +99,7 @@     "fusion_demo_progress",     "fusion_describe",     "fusion_detect_layers",+    "fusion_diagnostics",     "fusion_dialog_log",     "fusion_diff_pairs",     "fusion_dismiss_blocking_dialogs",@@ -150,6 +151,7 @@     "fusion_find_symbol",     "fusion_footprint_from_land_pattern",     "fusion_footprint_primitives",+    "fusion_foreground",     "fusion_generate_body",     "fusion_generate_package",     "fusion_get_app_state",@@ -166,6 +168,7 @@     "fusion_install",     "fusion_install_fusion",     "fusion_install_library_bundle",+    "fusion_instances",     "fusion_interactive",     "fusion_jlcpcb_order",     "fusion_kill",@@ -234,6 +237,7 @@     "fusion_prefs_close",     "fusion_prefs_open",     "fusion_prewarm",+    "fusion_progress",     "fusion_project_create",     "fusion_project_info",     "fusion_project_rename",@@ -521,6 +525,10 @@       "project_snapshot": 300,       "project_create": 180,       "test_board": 180,+      "diagnostics": 30,+      "instances": 10,+      "progress": 10,+      "foreground": 30,       "silkscreen_prep": 300,       "variants": 120,       "symbol_prims": 30,
contracts/describe.json+65
@@ -1669,6 +1669,23 @@       "summary": "Detect 2-layer vs 4-layer board (ULP + CAM comparison).",       "timeoutSeconds": 90     },+    {+      "example": {},+      "input": {},+      "longRunning": false,+      "name": "fusion_diagnostics",+      "output": {+        "addin": "{answering, activeDocument}",+        "bridge": "{version, exe, bundledAddin, cacheDir, stateDir}",+        "env": "{name: value}",+        "installs": "{fusionLauncher}",+        "running": "[{pid, image}]",+        "windows": "{mainTitle, needsSignin, licensingDialog, windows}"+      },+      "statusVerb": null,+      "summary": "Facts for a bug report (the standard's diagnostics), read without driving Fusion: the bridge's version, exe, bundled add-in and folders; the Fusion launcher found; Fusion's processes and windows (sign-in or licensing box up); whether the add-in answers and on which document; and the Adom, Fusion and Autodesk environment variables, with the values of any named like a credential never shown.",+      "timeoutSeconds": 30+    },     {       "example": {},       "input": {@@ -2770,6 +2787,24 @@       "summary": "Geometry of one placed part (the standard's footprint_primitives): its pads (with mask and paste), wires and arcs, and texts, placed on the board in mm and mil with layer ids; other kinds are counted.",       "timeoutSeconds": 120     },+    {+      "example": {+        "reason": "Showing you the routed board you asked to see"+      },+      "input": {+        "reason": "a sentence of 10 characters or more, written for the person at the PC (required)"+      },+      "longRunning": false,+      "name": "fusion_foreground",+      "output": {+        "hwnd": "int",+        "title": "str",+        "windowEtiquette": "str"+      },+      "statusVerb": null,+      "summary": "Bring Fusion in front of the user (the standard's foreground): the one sanctioned exception to running in the background. The user is told why in a notification and given three seconds before the window comes forward.",+      "timeoutSeconds": 30+    },     {       "example": {         "dimensionsMm": {@@ -3146,6 +3181,19 @@       "summary": "COMPOSITE, the Fusion twin of kicad_install_library_bundle (#68): ONE call takes a staged Cloud Library bundle (zip of <Lib>.lbr + <Part>.step models, or a bare lbrPath with STEPs beside it), unpacks it safely, places it under ~/Documents/Adom Libraries/<name>/, pairs EVERY deviceset with its STEP by name (deviceset, package, normalised, prefix; a one-part library pairs its single model regardless), lints part numbers (partMetadata: MPN, manufacturer, Mouser/DigiKey/LCSC - reported, never blocking), pre-flights Fusion state (fusion{status}), and starts the same bind -> prove chain fusion_create_library runs. ASYNC BY CONTRACT: returns IMMEDIATELY with runId + statusVerb because a STEP bind is 3-15 minutes and cannot fit a relay window; poll fusion_prove_result (measured percent, four streamed proof frames per part, provedParts[], and an addin_restart_required payload the CALLER turns into the ask). PARTIAL SHIPS: a deviceset with no model is a NAMED skip in unbound[], the rest still bind; partsBound < partsTotal is the signal. Each part is minted on its own single-deviceset library (the Package3D generator loads the FIRST package of the open library, so a merged library would put every chip on part one's footprint), with the STEP placed exactly as published (orient:false, origin trusted), then all urns are bound into the merged library, and after prove the bound library is SAVED TO THE CLOUD under <project>/Adom Libraries so it appears in Fusion's Library Manager (cloudLibrary{saved, fileId, duplicatesBefore} in fusion_prove_result). Per-part scaffolding is deleted; the user is left with one library plus its per-package f3d folder. capture:false only places the files and returns. This is the one contract the wiki CLI (`library install --format fusion`) and the Export button both read; the recipe has one owner.",       "timeoutSeconds": 60     },+    {+      "example": {},+      "input": {},+      "longRunning": false,+      "name": "fusion_instances",+      "output": {+        "count": "int",+        "instances": "[{pid, image, parentPid}]"+      },+      "statusVerb": null,+      "summary": "Every running Fusion (the standard's instances): pid, image and parent of each Fusion process. Fusion runs one instance per user, so verbs drive the one running.",+      "timeoutSeconds": 10+    },     {       "example": {},       "input": {@@ -19829,6 +19877,23 @@       "summary": "Warm the bridge ahead of the first real call, WITHOUT starting Fusion: confirms the add-in is synced into every Fusion add-in folder, readies the space-free staging folder, and caches the PCB design-rules catalog from adom/pcb-design-rules. Adom Bridge calls it once per installed version in the background (bridge.json prewarm.verb); safe to call again any time. Launching Fusion is never part of it.",       "timeoutSeconds": 120     },+    {+      "example": {},+      "input": {+        "recent": "how many recent runs, default 10"+      },+      "longRunning": false,+      "name": "fusion_progress",+      "output": {+        "active": "[{verb, runningSec, estimateSec}]",+        "history": "{phase: {runs, p50, max}}",+        "recentRuns": "[{at, verb, seconds, ok}]",+        "verbTimes": "{verb: {runs, p50, max}}"+      },+      "statusVerb": null,+      "summary": "The bridge's timing book (the standard's progress): the long operation in flight now with how long it has run and its usual time, the measured phase timings, per-verb run times (p50, max) and the most recent runs.",+      "timeoutSeconds": 10+    },     {       "example": {         "dryRun": true,
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.13.22"+version = "1.13.23" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.13.22"+version = "1.13.23" edition = "2021"  [dependencies]
describe.py+16
@@ -430,6 +430,22 @@ _T = [      {"expectDocument": "required", "moveText": "bool, default true", "dryRun": "bool"},      {"before": "{designatorsOnPads}", "after": "{designatorsOnPads}", "moved": "int", "stillOnPads": "[{ref, pad}]", "unplaced": "int", "silkOffCopper": "bool (designators)", "silkOutsideBoard": "[{kind, x, y}]"},      300, None, False, {"dryRun": True}),+    ('fusion_diagnostics', "Facts for a bug report (the standard's diagnostics), read without driving Fusion: the bridge's version, exe, bundled add-in and folders; the Fusion launcher found; Fusion's processes and windows (sign-in or licensing box up); whether the add-in answers and on which document; and the Adom, Fusion and Autodesk environment variables, with the values of any named like a credential never shown.",+     {},+     {"bridge": "{version, exe, bundledAddin, cacheDir, stateDir}", "installs": "{fusionLauncher}", "running": "[{pid, image}]", "addin": "{answering, activeDocument}", "windows": "{mainTitle, needsSignin, licensingDialog, windows}", "env": "{name: value}"},+     30, None, False, {}),+    ('fusion_instances', "Every running Fusion (the standard's instances): pid, image and parent of each Fusion process. Fusion runs one instance per user, so verbs drive the one running.",+     {},+     {"count": "int", "instances": "[{pid, image, parentPid}]"},+     10, None, False, {}),+    ('fusion_progress', "The bridge's timing book (the standard's progress): the long operation in flight now with how long it has run and its usual time, the measured phase timings, per-verb run times (p50, max) and the most recent runs.",+     {"recent": "how many recent runs, default 10"},+     {"active": "[{verb, runningSec, estimateSec}]", "history": "{phase: {runs, p50, max}}", "verbTimes": "{verb: {runs, p50, max}}", "recentRuns": "[{at, verb, seconds, ok}]"},+     10, None, False, {}),+    ('fusion_foreground', "Bring Fusion in front of the user (the standard's foreground): the one sanctioned exception to running in the background. The user is told why in a notification and given three seconds before the window comes forward.",+     {"reason": "a sentence of 10 characters or more, written for the person at the PC (required)"},+     {"hwnd": "int", "title": "str", "windowEtiquette": "str"},+     30, None, False, {"reason": "Showing you the routed board you asked to see"}),     ('fusion_test_board', "Footprints from a .lbr on ONE board (the standard's test_board): a NEW board (wMm x hMm outline, or one that fits the row) when pcbPath is new or omitted, APPENDED right of the current parts when pcbPath is an existing .brd, designators continuing (U4...). Written as EAGLE source, opened in Fusion as a local design dialog-free, and read back. assigns and stubs apply to the LAST part placed.",      {"libraryPath": "a .lbr on the Fusion machine (required)", "footprintName": "str | 'A;B;C' (required)", "pcbPath": "a .brd: existing = append, new = create (default %TEMP%/adom-test-boards/<name>.brd)", "wMm": "number (new board only)", "hMm": "number (new board only)", "assigns": "pad=NET;... (last part)", "stubs": "pad=x,y;... mm from the part's origin (last part): a 0.2 mm top track", "prefix": "designator prefix, default U", "layers": "2|4|6|8 (new board)", "open": "bool, default true"},      {"appended": "bool", "pcb": "path", "placed": "'U1;U2'", "count": "int (parts on the board)", "existingParts": "int", "padsOnComp": "int", "bodiesOnComp": "int", "saved": "bool (checked on disk)", "documentName": "str", "unknownPins": "[]"},
install.sh+1−1
@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.13.22"+DASH_VERSION="1.13.23" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.13.22",+  "version": "1.13.23",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {
page.json+2−2
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.13.22",+  "version": "1.13.23",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {@@ -152,4 +152,4 @@   "confirmed_user_skills": [     "fusion-design-efficiency"   ]-}\ No newline at end of file+}
rust/Cargo.lock+5−5
@@ -19,7 +19,7 @@ dependencies = [  [[package]] name = "adom-fusion-bridge"-version = "1.13.22"+version = "1.13.23" dependencies = [  "adom-eda-checks",  "base64",@@ -299,7 +299,7 @@ dependencies = [  [[package]] name = "fusion-core"-version = "1.13.22"+version = "1.13.23" dependencies = [  "serde",  "serde_json",@@ -310,7 +310,7 @@ dependencies = [  [[package]] name = "fusion-lbr"-version = "1.13.22"+version = "1.13.23" dependencies = [  "regex",  "roxmltree",@@ -322,14 +322,14 @@ dependencies = [  [[package]] name = "fusion-os-mac"-version = "1.13.22"+version = "1.13.23" dependencies = [  "fusion-core", ]  [[package]] name = "fusion-os-win"-version = "1.13.22"+version = "1.13.23" dependencies = [  "fusion-core",  "windows-sys 0.59.0",
rust/Cargo.toml+1−1
@@ -4,7 +4,7 @@ members = ["core", "lbr", "os-win", "os-mac", "bridge"]  # One version for the whole bridge, in lockstep with BRIDGE_VERSION (tools/release.sh bumps both). [workspace.package]-version = "1.13.22"+version = "1.13.23" edition = "2021" license = "MIT" 
rust/bridge/src/appverbs.rsadded+95
@@ -0,0 +1,95 @@+//! The standard's app-tier verbs in Fusion's terms: diagnostics (facts for a bug report), instances+//! (every running Fusion), progress (the bridge's timing book and what is in flight) and foreground+//! (Fusion brought in front, the sanctioned exception, with the user told first).++use serde_json::{json, Value};+use std::path::Path;++use fusion_core::ab::{AbClient, Caller, Loopback};++/// Environment variable names that may hold a credential: their values are never shown.+fn secret(name: &str) -> bool {+    let n = name.to_uppercase();+    ["TOKEN", "KEY", "SECRET", "PASS", "TICKET", "AUTH", "COOKIE", "CREDENTIAL", "SESSION"].iter().any(|w| n.contains(w))+}++/// `fusion_instances`: every running Fusion process (Fusion runs one per user; its helper+/// processes are listed under their parent).+pub fn instances(procs: &[fusion_core::host::FusionProcess]) -> Value {+    let rows: Vec<Value> = procs.iter().map(|p| json!({"pid": p.pid, "image": p.image, "parentPid": p.parent_pid})).collect();+    let n = rows.len();+    json!({"success": true, "mutated": false, "count": n, "instances": rows,+           "_hint": if n == 0 { "Fusion is not running: fusion_start launches it." } else { "Fusion runs one instance per user; verbs drive the one that is running." }})+}++/// `fusion_progress`: what the bridge is doing right now (its long operation, if any), the phase+/// timings it has measured and its recent verb runs.+pub fn progress(state_dir: &Path, long_op: Option<String>, since: f64, recent: usize) -> Value {+    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs_f64()).unwrap_or(0.0);+    let active: Vec<Value> = long_op.map(|op| json!({"verb": op, "runningSec": if since > 0.0 { ((now - since) * 10.0).round() / 10.0 } else { 0.0 },+        "estimateSec": crate::timings::verb_estimate(state_dir, &op).0})).into_iter().collect();+    let book = crate::timings::load(state_dir);+    let mut verb_times = serde_json::Map::new();+    let mut history = serde_json::Map::new();+    for (k, v) in book.as_object().into_iter().flatten() {+        let s: Vec<f64> = v["samples"].as_array().into_iter().flatten().filter_map(|x| x.as_f64()).collect();+        if s.is_empty() { continue; }+        let mut sorted = s.clone(); sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());+        let row = json!({"runs": s.len(), "p50": sorted[sorted.len() / 2], "max": sorted[sorted.len() - 1], "lastAt": v["lastAt"]});+        match k.strip_prefix("verb:") { Some(verb) => { verb_times.insert(verb.trim_end_matches("|any").to_string(), row); } None => { history.insert(k.clone(), row); } }+    }+    let last: Vec<Value> = std::fs::read_to_string(state_dir.join("verb-times.jsonl")).unwrap_or_default().lines().rev().take(recent).filter_map(|l| serde_json::from_str(l).ok()).collect();+    json!({"success": true, "mutated": false, "active": active, "history": history, "verbTimes": verb_times, "recentRuns": last})+}++/// `fusion_diagnostics`: the facts a bug report needs, read without driving Fusion.+pub fn diagnostics(procs: &[fusion_core::host::FusionProcess], exe: Option<std::path::PathBuf>, state_dir: &Path, caller: &Caller) -> Value {+    let env: serde_json::Map<String, Value> = std::env::vars()+        .filter(|(k, _)| { let u = k.to_uppercase(); u.starts_with("ADOM") || u.contains("FUSION") || u.contains("AUTODESK") || ["APPDATA", "LOCALAPPDATA", "USERPROFILE", "PROCESSOR_ARCHITECTURE", "OS"].contains(&u.as_str()) })+        .map(|(k, v)| { let shown = if secret(&k) { json!("(set, not shown)") } else { json!(v) }; (k, shown) }).collect();+    let windows = Loopback::from_env().map(|ab| { let f = fusion_core::windows_facts::facts_from_ab(&ab, caller); json!({"mainTitle": f.main_title, "mainHwnd": f.main_hwnd, "needsSignin": f.needs_signin, "licensingDialog": f.licensing_dialog, "windows": f.family}) }).unwrap_or(json!(null));+    let addin = crate::addin::addin_http("get_app_state", &json!({}), 3);+    let manifest = |p: &Path| std::fs::read_to_string(p).ok().and_then(|t| serde_json::from_str::<Value>(&t).ok()).map(|v| v["version"].clone()).unwrap_or(Value::Null);+    let here = std::env::current_exe().ok().and_then(|e| e.parent().map(|d| d.to_path_buf()));+    json!({"success": true, "mutated": false,+           "bridge": {"version": env!("CARGO_PKG_VERSION"), "exe": std::env::current_exe().ok().map(|p| p.to_string_lossy().to_string()),+                      "bundledAddin": here.as_ref().map(|d| manifest(&d.join("addin").join("AdomBridge").join("AdomBridge.manifest"))), "cacheDir": here.map(|d| d.to_string_lossy().to_string()),+                      "stateDir": state_dir.to_string_lossy()},+           "installs": {"fusionLauncher": exe.map(|p| p.to_string_lossy().to_string())},+           "running": procs.iter().map(|p| json!({"pid": p.pid, "image": p.image})).collect::<Vec<_>>(),+           "addin": {"answering": addin["success"].as_bool() == Some(true), "activeDocument": addin["data"]["activeDocument"], "error": addin["error"]},+           "windows": windows, "env": env,+           "_hint": "Facts for a bug report, read without driving Fusion. Values of variables named like credentials are never shown. fusion_action_ledger has what the bridge did lately; fusion_progress its timings."})+}++/// `fusion_foreground {reason}`: Fusion brought in front of the user, the one sanctioned exception+/// to running in the background. The user is told why first and given three seconds to yield.+pub fn foreground(args: &Value, state_dir: &Path, caller: &Caller) -> Value {+    let reason = args["reason"].as_str().or(args["foregroundReason"].as_str()).unwrap_or("").trim().to_string();+    if reason.chars().count() < 10 {+        return json!({"success": false, "errorCode": "foreground_reason_required", "error": "reason is required: a sentence of 10 characters or more, written for the person at the PC",+                      "_hint": "Fusion verbs run in the background so the user is never interrupted. Bring it forward only when the user wants to watch, and say why in words they will read."});+    }+    let Some(ab) = Loopback::from_env() else { return json!({"success": false, "errorCode": "ab_unreachable", "error": "Adom Bridge did not answer on this machine"}) };+    let facts = fusion_core::windows_facts::facts_from_ab(&ab, caller);+    let Some(hwnd) = facts.main_hwnd else { return json!({"success": false, "errorCode": "fusion_not_running", "error": "no Fusion window to bring forward", "_hint": "fusion_start launches it."}) };+    crate::etiquette::note_focus_event(state_dir, "foreground", "foreground", &reason);+    let _ = ab.call("notify_user", json!({"title": "Fusion is coming to the foreground", "body": format!("{reason} (Requested by your AI - Fusion verbs otherwise always run in the background.)"), "level": "info", "target": "all"}), caller, 10);+    std::thread::sleep(std::time::Duration::from_secs(3));+    let r = ab.call("desktop_bring_to_front", json!({"hwnd": hwnd}), caller, 15);+    let ok = r.as_ref().map(|v| v["success"].as_bool() != Some(false)).unwrap_or(false);+    json!({"success": ok, "mutated": false, "hwnd": hwnd, "title": facts.main_title, "windowEtiquette": "foreground (sanctioned, the user was told first)",+           "error": if ok { Value::Null } else { json!(r.err().map(|e| e.to_string()).unwrap_or_else(|| "ab could not bring the window forward".into())) }})+}++#[cfg(test)]+mod tests {+    use super::*;+    #[test]+    fn credential_names_are_never_shown() {+        assert!(secret("ADOM_API_KEY") && secret("ADOM_SESSION") && secret("FUSION_AUTH_TOKEN") && secret("password_file"));+        assert!(!secret("ADOM_AI_THREAD") && !secret("LOCALAPPDATA"));+        let p = progress(Path::new("/nonexistent"), Some("export_gerbers".into()), 0.0, 5);+        assert_eq!(p["active"][0]["verb"], "export_gerbers");+    }+}
rust/bridge/src/main.rs+5
@@ -39,6 +39,7 @@ mod boardgen; mod partimport; mod unsupported; mod replicate;+mod appverbs; mod livefeed; mod chainexec; mod epg;@@ -496,6 +497,10 @@ fn main() {                     // Phase 3 shadow: the sidecar still answers; the exe computes its own readiness                     // from real probes and rides beside it with a diff, until parity holds.                     "fusion_readiness" | "readiness" if readiness_owner_is_exe(&**h) => owned_readiness(&**h, &sidecar, &caller),+                    "fusion_instances" | "instances" => appverbs::instances(&h.fusion_processes()),+                    "fusion_progress" | "progress" => appverbs::progress(&h.bridge_state_dir(), LONG_OP.lock().unwrap().clone(), *LONG_OP_SINCE.lock().unwrap(), parsed["args"]["recent"].as_u64().unwrap_or(10) as usize),+                    "fusion_diagnostics" | "diagnostics" => appverbs::diagnostics(&h.fusion_processes(), h.fusion_executable(), &h.bridge_state_dir(), &caller),+                    "fusion_foreground" | "foreground" => appverbs::foreground(&parsed["args"], &h.bridge_state_dir(), &caller),                     "fusion_create_library" | "create_library" if chains_owner_is_exe(&**h) => chains::create_library(Arc::new(ExeChainHost { sidecar: sidecar.clone(), caller: caller.clone(), state_dir: h.bridge_state_dir() }), parsed["args"].clone()),                     "fusion_install_library_bundle" | "install_library_bundle" if chains_owner_is_exe(&**h) => chains::install_library_bundle(Arc::new(ExeChainHost { sidecar: sidecar.clone(), caller: caller.clone(), state_dir: h.bridge_state_dir() }), parsed["args"].clone()),                     "fusion_prove_result" | "prove_result" if chains_owner_is_exe(&**h) => { let mut r = chains::prove_result(&parsed["args"]); r["shell"] = json!("rust"); r },
rust/bridge/src/unsupported.rs+2
@@ -27,6 +27,8 @@ pub const TABLE: &[(&str, &str)] = &[     ("symbol_mode", "an EAGLE symbol has one look: there are no display modes to show"),     ("cleanup_libs", "a new Fusion library starts empty: there is no factory-default part to remove"),     ("plate_vias", "EAGLE pads are always plated: an unplated hole is a separate hole object"),+    ("panel", "Altium's docked panel tabs; Fusion's editors show their own panels, and the bridge opens a surface (library, symbol, footprint, board, schematic, 3D) with fusion_show_* in the background"),+    ("selection", "Fusion does not show the user's board selection to the bridge: a ULP's ingroup() reads no part even right after a GROUP (checked live on Fusion 2706, 2026-10-30). Name the parts instead; fusion_electron_select shows one to the user"),     ("component_classes", "an EAGLE board has net classes (fusion_net_classes, fusion_add_rule) but no component classes or rooms: group a channel by its designators (fusion_replicate_layout takes them as lists)"), ]; 
tests/run_verb_tests.py+5
@@ -463,6 +463,11 @@ t("fusion_set_visibility", "daily", {"libraryPath": "C:/Users/olive/Documents/fu t("fusion_variants", "daily", dict(B), {"ok": True, "fields": ["variants"]}, "the design's assembly variants (BQ25792 has none)") t("fusion_variants#edit", "negative", dict(B, name="FL-TEST", notFitted=["C11"]), {"pred": "must_refuse"}, "Fusion's VARIANT text command changes nothing, so an edit is refused not_supported") t("fusion_silkscreen_prep", "daily", dict(B, dryRun=True), {"ok": True, "fields": ["before", "after", "silkOffCopper"]}, "designators on pads counted and a move planned (dry run)")+t("fusion_diagnostics", "daily", {}, {"ok": True, "fields": ["bridge", "installs", "running", "addin", "env"]}, "bug-report facts, no Fusion driving; credential values never shown")+t("fusion_instances", "daily", {}, {"ok": True, "fields": ["count", "instances"]}, "Fusion's processes")+t("fusion_progress", "daily", {}, {"ok": True, "fields": ["active", "verbTimes"]}, "the bridge's timing book")+t("fusion_foreground", "manual", {"reason": "battery check of the foreground verb"}, {"ok": True}, "brings Fusion in front: manual only, never in an unattended battery")+t("fusion_foreground#noreason", "negative", {}, {"pred": "must_refuse"}, "a foreground with no reason is refused") t("fusion_build_board", "daily", {"libraryPath": BOARDLIB, "pcbPath": BOARDDIR + "/build.brd", "parts": [{"ref": "R1", "footprint": "R0603", "x": 5, "y": 5}, {"ref": "Q1", "footprint": "SOT23", "x": 12, "y": 5}], "nets": {"GND": ["R1.2", "Q1.2"]}, "outline": {"rect": [0, 0, 18, 10], "cornerRadius": 1}, "open": False}, {"ok": True, "fields": ["pcbPath", "unknownPins"]}, "written offline (open:false keeps the battery from opening a design each run)") t("fusion_build_board#missing", "negative", {"libraryPath": BOARDLIB, "pcbPath": BOARDDIR + "/bad.brd", "parts": [{"ref": "U1", "footprint": "NO_SUCH_FP", "x": 0, "y": 0}], "open": False}, {"pred": "must_refuse"}, "a footprint missing from the library refuses the whole build") t("fusion_test_board", "daily", {"libraryPath": BOARDLIB, "footprintName": "R0603;SOT23", "pcbPath": BOARDDIR + "/test.brd", "open": False}, {"ok": True, "fields": ["pcb", "placed", "saved"]}, "test.brd is made on the first run and appended to after (designators continue); written offline")