app
Adom Library
Public Made by Adomby adom
adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.
← Commit history
2.13.0: port fallback so a foreign occupant cannot kill the dock launch; Send to AI un-parked with a shell-version guard
9 files changed
+97−25
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.12.7"+version = "2.13.0" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.12.7"+version = "2.13.0" edition = "2021" description = "adom-lbr generator / EDA translator: the canonical adom-lbr JSON is the hub, with native converters to Altium (.SchLib/.PcbLib/.IntLib), EAGLE/Fusion (.lbr), and KiCad. Altium output is written natively — no Altium needed at conversion time."
VERSION+1−1@@ -1 +1 @@-2.12.7+2.13.0
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.12.7",+ "version": "2.13.0", "type": "app", "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {},
page.json+9−12@@ -1,8 +1,8 @@ { "slug": "adom-lbr",- "version": "2.12.7",+ "version": "2.13.0", "type": "app",- "description": "adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",+ "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {}, "tags": [ "eda",@@ -52,24 +52,21 @@ ], "files": [ "bin/**",- "assets/**", "SKILL.md",- "skills/**", "README.md", "install.sh", "uninstall.sh",- "screenshots/**",- "dockbar.json"+ "screenshots/**" ],- "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools — KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion — through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",+ "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools \u2014 KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion \u2014 through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.", "sample_prompts": [ { "prompt": "Convert this KiCad symbol and footprint to an Altium SchLib and PcbLib",- "label": "KiCad → Altium"+ "label": "KiCad \u2192 Altium" }, { "prompt": "Import this Altium footprint into the adom-lbr JSON",- "label": "Altium → JSON"+ "label": "Altium \u2192 JSON" }, { "prompt": "Bundle this part into a single Altium IntLib",@@ -77,15 +74,15 @@ }, { "prompt": "Convert this Altium SchLib and PcbLib to a KiCad symbol and footprint",- "label": "Altium → KiCad"+ "label": "Altium \u2192 KiCad" }, { "prompt": "Export this adom-lbr JSON to a KiCad .kicad_sym and .kicad_mod",- "label": "JSON → KiCad"+ "label": "JSON \u2192 KiCad" }, { "prompt": "Open the adom-lbr Manager to browse wiki components and view the layer stackup", "label": "Browse parts" } ]-}+}
src/dock.rs+61−7@@ -178,12 +178,36 @@ fn control_api_base() -> String { /// thread named for the app and tagged dock:adom-lbr, then submit the phrase. /// NEW THREADS ONLY - resuming or typing into existing threads stays parked /// (try_phrase below remains dormant behind TRY_PARKED).-/// EMERGENCY PARK (dock thread, 2026-08-24): the Send-to-AI success path-/// crashed the ah dev shell on ConfRoomROG during kicad's cold-verify. Until-/// the un-park broadcast after main's certification, /api/say-send refuses-/// BEFORE any control-API call; the page's failure path (error toast + copy-/// to clipboard) carries users. Flip this to false on the broadcast.-pub const SAY_SEND_PARKED: bool = true;+/// UN-PARKED 2026-08-24 on John's ruling, after the ah build thread certified+/// the crash family dead on the gate-fixed build (33ac2074 gated six raw-CDP+/// typing helpers that ran outside the concurrency gate) and the dock thread+/// verified the installed binary by code marker rather than the version stamp.+/// Kept as a const so a future park is one line again.+pub const SAY_SEND_PARKED: bool = false;++/// Hydrogen builds BEFORE the gate fix still carry the crash, and published+/// 1.0.141 predates it. A pre-fix shell gets an honest refusal here rather+/// than a dead editor; the page then copies the phrase as it always did.+/// Un-gated once every user is past 1.0.142.+const MIN_SHELL_VERSION: (u32, u32, u32) = (1, 0, 142);++fn parse_ver(v: &str) -> Option<(u32, u32, u32)> {+ let core = v.trim().trim_start_matches('v');+ let core = core.split(|c: char| c == '-' || c == '+' || c == ' ').next()?;+ let mut it = core.split('.').map(|p| p.parse::<u32>().ok());+ Some((it.next()??, it.next()??, it.next().flatten().unwrap_or(0)))+}++/// The shell's version, from the control API. None means "could not tell",+/// which is NOT treated as too old: an unknown shell still gets the attempt,+/// because the crash is the exception and refusing everyone on a probe miss+/// would break the feature for everybody.+fn shell_version(base: &str) -> Option<(u32, u32, u32)> {+ let v = curl_json(&format!("{base}/version"), None, 2)?;+ let s = v.get("version").or_else(|| v.get("hydrogenVersion")).or_else(|| v.get("app"))+ .and_then(|x| x.as_str())?;+ parse_ver(s)+} pub fn say_send(phrase: &str) -> Value { if phrase.trim().is_empty() {@@ -194,6 +218,12 @@ pub fn say_send(phrase: &str) -> Value { "why": "Send to AI is parked fleet-wide while a crash in the AI panel's send path is fixed. Copied instead; paste it into any AI thread."}); } let base = control_api_base();+ if let Some(v) = shell_version(&base) {+ if v < MIN_SHELL_VERSION {+ return json!({"ok": false, "parked": true,+ "why": format!("This Hydrogen ({}.{}.{}) predates the fix for a crash in the AI panel's send path; update to 1.0.142 or newer. Copied instead; paste it into any AI thread.", v.0, v.1, v.2)});+ }+ } let new_body = json!({"name": "Adom LBR — say it", "tags": ["dock:adom-lbr"]}); let Some(created) = curl_json(&format!("{base}/ai-threads/new"), Some(&new_body), 4) else { return json!({"ok": false, "why": "the AI panel's control API is not reachable from this machine"});@@ -589,6 +619,28 @@ fn ask_server_to_claim(port: u16) { /// The dock entry point. Returns Ok(true) when a server is (now) serving and /// the caller should exit 0; the foreground serve itself is the caller's job /// when this returns Ok(false).+/// The port this process will actually serve on: the requested one, or the+/// next free port when a FOREIGN service holds it.+///+/// Found live on John's box (2026-08-25): another Adom app had taken 8785, so+/// the old guard refused with "pick another with --port" and the DOCK LAUNCH+/// DIED - correct diagnosis, useless outcome, because the human never types+/// that command; the dock does, and it scrapes whatever URL we print. A+/// foreign occupant is now a reason to move over, announced on stderr so the+/// launch console says why.+pub fn resolve_port(requested: u16) -> anyhow::Result<u16> {+ if running_version(requested).is_some() || !port_in_use(requested) || !serve_pids(requested).is_empty() {+ return Ok(requested); // ours, or free: the normal paths handle it+ }+ for p in (requested + 1)..=(requested + 20) {+ if !port_in_use(p) {+ eprintln!("adom-lbr dashboard: port {requested} is held by another service; serving on {p} instead");+ return Ok(p);+ }+ }+ anyhow::bail!("port {requested} is taken by another service and no free port was found in {}..{}", requested + 1, requested + 20)+}+ pub fn serve_entry(port: u16, print_url: bool, foreground: bool) -> anyhow::Result<bool> { let mut theirs = running_version(port); // A pre-2.11 Manager has no /api/status, so it cannot identify itself; but@@ -597,7 +649,9 @@ pub fn serve_entry(port: u16, print_url: bool, foreground: bool) -> anyhow::Resu // timed out means free, and we simply bind. if theirs.is_none() && port_in_use(port) { if serve_pids(port).is_empty() {- anyhow::bail!("port {port} is taken by something that is not adom-lbr; pick another with --port");+ // Should be unreachable: resolve_port moved us off a foreign port+ // before we got here. Kept as a guard rather than a silent bind.+ anyhow::bail!("port {port} is taken by something that is not adom-lbr"); } if !retire_stale(port, "pre-2.11") { anyhow::bail!("an older adom-lbr Manager owns port {port} and would not exit; kill it and relaunch");
src/main.rs+6@@ -2044,6 +2044,12 @@ fn main() { } Commands::Dashboard { cmd } => match cmd { DashboardCmd::Serve { port, print_url, foreground } => {+ // A foreign service on the default port moves us over rather+ // than killing the launch (John's box, 2026-08-25).+ let port = match dock::resolve_port(port) {+ Ok(p) => p,+ Err(e) => { eprintln!("ERROR: {e}"); process::exit(2); }+ }; match dock::serve_entry(port, print_url, foreground) { Ok(true) => {} Ok(false) => {
src/manage.html+9−1@@ -3,7 +3,7 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1">-<title>adom-lbr Dashboard</title>+<title>Adom LBR Dashboard</title> <link rel="icon" href="favicon.svg" type="image/svg+xml"> <script type="module" src="https://unpkg.com/@google/[email protected]/dist/model-viewer.min.js"></script> <style>@@ -796,6 +796,14 @@ async function refreshStatus(force){ try{ const s=await api('api/status'+(force?'?force=1':''));lastStatus=s; document.getElementById('pop-ver').textContent='v'+s.version;+ // Cache cross-check (dockbar.md, 2026-08-24): if the served build moved on+ // while this document stayed cached, say so loudly rather than let anyone+ // screenshot stale UI that reports a fresh version.+ if(!window.__buildAtLoad)window.__buildAtLoad=s.version;+ else if(window.__buildAtLoad!==s.version&&!window.__staleWarned){+ window.__staleWarned=true;+ toast('This page is running '+window.__buildAtLoad+' but the server now serves '+s.version+'. Reload to catch up.',true);+ } paintLeds(s); paintSettingsStatus(); gradLaunchTick(s);
src/manage.rs+8−1@@ -1265,8 +1265,15 @@ fn export_part(slug: &str) -> Value { fn hdr(ct: &str) -> Header { Header::from_bytes(&b"Content-Type"[..], ct.as_bytes()).unwrap() }+/// The dashboard HTML is served no-store (dockbar.md, 2026-08-24): a webview+/// can keep a CACHED page across a version change, so the server reports the+/// new version while the page still runs the OLD build's JavaScript - which is+/// how a sibling app screenshotted stale UI after a clean Hard Refresh Open.+/// Hashed static assets may still cache; this document must not. fn html(s: &str) -> Response<Cursor<Vec<u8>>> {- Response::from_string(s).with_header(hdr("text/html; charset=utf-8"))+ Response::from_string(s)+ .with_header(hdr("text/html; charset=utf-8"))+ .with_header(Header::from_bytes(&b"Cache-Control"[..], &b"no-store, must-revalidate"[..]).unwrap()) } fn json_resp(s: &str) -> Response<Cursor<Vec<u8>>> { Response::from_string(s).with_header(hdr("application/json"))