app
Altium - the Altium Bridge
Public Made by Adomby adom
Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.
← Commit history
Publish 0.1.1
10 files changed
+3262−7
dashboard/altium_dashboard.pyadded+1572@@ -0,0 +1,1572 @@+#!/usr/bin/env python3+"""altium-dashboard - the Altium bridge's Launch Page.++Parity sibling of fusion-dashboard (adom/fusion-bridge dashboard/src/main.rs):+same LED-with-_hint model, same live channel, same AI-drivability contract, same+visual system. Status comes from shelling `adom-bridge`, so the BRIDGE stays the+single source of truth and this server owns no state the bridge could contradict.++Serves:+ GET / the app (dashboard/ui/index.html, read per request+ so a ralph loop only needs a browser reload)+ GET /favicon.svg+ GET /events SSE live channel: hello / state / toast / click+ GET /api/targets every connected box, lightly probed+ GET /api/status[/<t>[/force]] the LED payload (also /state)+ GET /console the server's own log lines (what the AI reads back)+ GET /health liveness + sseClients (live viewer count)+ POST /api/action {action,target,args}+ POST /ui/toast {message,kind} narrate OVER the running app+ POST /ui/click {target} the PAGE clicks its own control++CLI:+ altium-dashboard serve [--port 8931] [--target BOX] [--print-url] [--dev]+ altium-dashboard status [--target BOX]+ altium-dashboard ui toast "msg" [--kind ok|err|info]+ altium-dashboard ui click <setup|launch|close|demo|part-tour|settings|refresh>+ altium-dashboard ui state | ui console+ altium-dashboard open [--pup | --webview] [--target BOX]+ altium-dashboard health++--dev (or ALTIUM_DASHBOARD_DEV=1) adds the maintainer-only Daily suite LED. Normal+Adom users never run the nightly verb suite, so it is off by default.++PROXY NOTE (paid for live): adom.cloud STRIPS query strings on sub-paths, so the+target rides the PATH (/api/status/<target>/force), never ?target=. The SSE route+also needs the padding + no-transform headers or the proxy buffers every byte.+"""+import json+import os+import queue+import re+import shutil+import subprocess+import sys+import tempfile+import threading+import time+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer+from pathlib import Path+from urllib.parse import unquote, urlparse++VERSION = "0.1.1"+AI_THREAD = "altium-dashboard"+DEFAULT_PORT = 8932+UI_DIR = Path(__file__).resolve().parent / "ui"++# SUPERSEDED, and worth stating so nobody re-litigates it. John, 2026-08-18,+# told me to ship the REAL Altium logo ("go with the logo the user is familiar+# with and not try to reinvent the wheel") after I had invented a teal mark.+# John, 2026-08-24, looking at ah's webview tab strip where this blue filled+# tile sat beside white line icons: "this is awful. all of these should be our+# standard type of icon which is just a line drawing in white on a transparent+# background." The later ruling is narrower and explicit about THIS artifact,+# and adom-ui-design 1.10.0's `icons` skill made it fleet law (rule 4: never+# ship the wrapped vendor's mark - off-palette, filled, trademarked, and it+# turns the tab strip into a logo salad). So the identity icon is now our own+# line drawing of what a Altium user works on; the 08-18 rule survives only in+# its real scope, which was "do not invent a random abstract mark".+LOGO_PATH = UI_DIR / "dock-altium.svg"++# "Say it" (John via the dock thread, 2026-08-23): teach users how to ASK for+# this app by its noun, first and foremost, at the top of the dashboard. The+# list lives in dockbar.json (`say`) so the dock's interstitial and the+# dashboard teach the same phrases; the built-in copy is the fallback for an+# installed dashboard that ships without the manifest beside it.+_SAY_FALLBACK = [+ "open the symbol in altium",+ "send this footprint to altium",+ "show me the board in altium 3d",+ "open my project in altium",+ "install this part into altium",+ "export gerbers from altium",+]+++def say_phrases():+ for cand in (Path(__file__).resolve().parent.parent / "dockbar.json",+ Path.home() / ".local" / "share" / "adom" / "altium-bridge" / "dockbar.json"):+ try:+ lst = json.loads(cand.read_text(encoding="utf-8")).get("say")+ if isinstance(lst, list) and lst:+ return [str(x) for x in lst][:6]+ except Exception:+ continue+ return list(_SAY_FALLBACK)+++HYDROGEN_CONTROL = "http://127.0.0.1:47084"++++def _hydrogen_control_url():+ """The workspace control API base, from the well-known file the panel+ writes (~/.adom/hydrogen-control-url). None when this machine has no+ panel - the caller then falls back to copy."""+ try:+ u = (Path.home() / ".adom" / "hydrogen-control-url").read_text(+ encoding="utf-8").strip()+ return u.rstrip("/") or None+ except Exception:+ return None+++LAST_VIEWPORT = {}+VIEWPORT_LOG = []+MIN_SHELL_VERSION = (1, 0, 142) # the build that fixed the run-prompt kill+++def _ver_tuple(v):+ """Numeric version tuple. A LEXICAL compare passes 1.0.99 as >= 1.0.142+ (issue #59), so every comparison in this file goes through here."""+ out = []+ for part in str(v or "").strip().lstrip("v").split("."):+ digits = "".join(c for c in part if c.isdigit())+ out.append(int(digits) if digits else 0)+ return tuple(out[:3]) or (0,)+++# ---------------------------------------------------------------------------+# ROUTE TABLE. Two catch-alls used to live here and each one made a check pass+# for the wrong reason (fleet sweep, 2026-08-25):+# * do_GET rendered index.html for EVERY unmatched path, so a missing settings+# PNG, demo mp3 or favicon came back as HTML 200. The launch-console 404+# watch is the one thing watched on a virgin install, so the check that+# exists to catch a broken fresh install could never fire. It also made+# proxy-fidelity row 3 ("not upstream's 418") pass on blanket-SERVED paths.+# * do_OPTIONS answered 204 for EVERY path, so the module's checkSend probes+# the send route with OPTIONS and its "send route wired: not 404" row went+# green for routes that do not exist at all.+# A permissive default does not fail a check, it ANSWERS it. Both now consult+# this one table, so a route cannot be live for OPTIONS and dead for GET.+#+# Matching mirrors each handler EXACTLY, endswith included: the adom.cloud proxy+# mounts this app under a sub-path and forwards the prefix, so "/apps/altium/health"+# is the same route as "/health".+_EXACT_ROUTES = ("/health", "/console", "/events", "/api/targets",+ "/api/action", "/ui/toast", "/ui/click")+_SUFFIX_ROUTES = ("/newbie-panel.js", "/favicon.svg", "/favicon.png", "/favicon.ico",+ "/dock-altium.svg", "/altium-logo.png", "/updater/status",+ "/api/viewport", "/ai-threads/run-prompt", "/api/say-send")+_REGEX_ROUTES = (r"/assets/(.+)$",+ r"^/api/verb_times(?:/([^/]+))?$",+ r"^/(?:api/status|state)(?:/([^/]+))?(/force)?$")+# Namespaces we own: an unmatched path under one of these is a WRONG route, never+# an app URL, so it 404s even for a browser navigation.+_OWNED_PREFIXES = ("/api/", "/ui/", "/updater/", "/assets/", "/ai-threads/")+_ASSET_SUFFIXES = (".png", ".jpg", ".jpeg", ".webp", ".svg", ".ico", ".gif",+ ".mp3", ".wav", ".ogg", ".mp4", ".webm",+ ".js", ".mjs", ".css", ".map", ".json", ".woff", ".woff2", ".ttf")+++def _route_exists(path):+ """True if `path` reaches a real handler. Used by do_OPTIONS so a preflight+ cannot claim a route that GET/POST would not serve."""+ if path in _EXACT_ROUTES or path.endswith(_SUFFIX_ROUTES):+ return True+ return any(re.search(rx, path) for rx in _REGEX_ROUTES)+++def _wants_app_shell(handler, path):+ """The app is a single page, and under the proxy its root arrives WITH the+ mount prefix ("/apps/altium"), which is shape-identical to a typo. So the+ shell is served only for a real browser navigation, and never for a path+ that is asset-shaped or inside a namespace we own.++ Stated honestly because it is the load-bearing caveat: a browser typing a+ wrong extensionless URL still gets the shell. Every case the 404 watch+ actually watches - a missing png, mp3, favicon, or js - carries an extension+ or an owned prefix and 404s regardless of who asks."""+ if path == "/":+ return True # the app's own root, whatever client asks for it+ if path.endswith(_ASSET_SUFFIXES):+ return False+ if any(pre in path + "/" for pre in _OWNED_PREFIXES):+ return False+ # A browser NAVIGATION always sends text/html in Accept; curl and every+ # other tool send */* or nothing. That is the only signal separating the+ # proxy's mounted root from a typo, and it is why the one-line fleet test+ # (curl <base>/nonsense) gets an honest 404.+ return "text/html" in (handler.headers.get("Accept") or "")+++def _proxy_control(handler, path, body=None, timeout=20):+ """Forward one control-API call VERBATIM: same status, same bytes.++ Both halves matter. Re-serialising the JSON (json.loads then json.dumps)+ changes key order and whitespace, and hardcoding 200 hides an upstream 207+ or 404 from the caller - the module decides what to do from the status and+ the body it actually gets, so a proxy that "helpfully" normalises either is+ lying to it. Non-2xx arrives as HTTPError and is forwarded, not swallowed.+ (Proven with assets/tools/proxy-fidelity.py, which reds a re-serialising+ proxy in its own self-test before it judges yours.)"""+ import urllib.request, urllib.error+ base = _hydrogen_control_url()+ if not base:+ return handler._json({"ok": False, "why": "no control api on this machine"}, 503)+ req = urllib.request.Request(+ base + path, method="POST" if body is not None else "GET",+ data=json.dumps(body).encode("utf-8") if body is not None else None,+ headers={"Content-Type": "application/json"} if body is not None else {})+ try:+ with urllib.request.urlopen(req, timeout=timeout) as r:+ return handler._send(r.status, r.headers.get("Content-Type", "application/json"),+ r.read())+ except urllib.error.HTTPError as e:+ return handler._send(e.code, e.headers.get("Content-Type", "application/json"),+ e.read())+ except Exception as e:+ return handler._json({"ok": False, "why": f"{e.__class__.__name__}: {e}"}, 502)+++def say_send(phrase):+ """Send-to-AI (un-parked 2026-08-24 on John's ruling, after the ah build+ thread certified the run-prompt crash family dead on the gate-fixed build).++ ONE call, per the corrected contract in issue #58: POST /ai-threads/run-prompt+ with new:true. The earlier two-call flow (/ai-threads/new then run-prompt+ with its sid) was WRONG and shipped fleet-wide: /new has never returned an+ id, because a thread has no sid until its first submitted prompt, so the+ button opened a tab and delivered nothing.++ Still NEW threads only: resume and typing-into-existing stay parked.+ Every failure returns ok:False with a readable `why`; the page then+ error-toasts AND copies the phrase, so a user is never stranded."""+ import urllib.request++ base = _hydrogen_control_url()+ if not base:+ return {"ok": False, "why": "no AI panel on this machine (hydrogen-control-url missing)"}++ def _get(path, timeout):+ with urllib.request.urlopen(base + path, timeout=timeout) as r:+ return json.loads(r.read().decode("utf-8") or "{}")++ # SAFETY GATE (issue #59): a shell older than 1.0.142 still carries the+ # crash, so refuse honestly there instead of killing the user's editor.+ # Numeric compare, never lexical.+ try:+ st = _get("/updater/status", 8)+ d = st.get("data") if isinstance(st.get("data"), dict) else st+ cur = (d.get("updater") or d).get("current_version") or d.get("current_version")+ if cur and _ver_tuple(cur) < MIN_SHELL_VERSION:+ return {"ok": False, "shellTooOld": True, "currentVersion": str(cur),+ "why": f"your Hydrogen ({cur}) predates the Send to AI fix; update to "+ f"1.0.142 or newer to enable it. The phrase was copied instead."}+ except Exception:+ pass # cannot read the updater: fall through rather than block a healthy shell++ body = {"new": True, "prompt": phrase, "submit": True,+ "name": "Altium - say it", "tags": ["dock:altium-bridge"]}+ req = urllib.request.Request(base + "/ai-threads/run-prompt",+ data=json.dumps(body).encode("utf-8"), method="POST",+ headers={"Content-Type": "application/json"})+ try:+ # 120s: creating the tab alone can take 60s. NEVER retry blind on a+ # timeout - a retry can open a second tab for one click.+ with urllib.request.urlopen(req, timeout=120) as r:+ res = json.loads(r.read().decode("utf-8") or "{}")+ except Exception as e:+ return {"ok": False, "why": f"the AI panel did not answer ({e.__class__.__name__}). "+ f"Not retried, so no duplicate tab. The phrase was copied instead."}++ d = res.get("data") if isinstance(res.get("data"), dict) else res+ # VERIFY BY DELIVERY, never by the thread's name (issue #59: a correctly+ # named tab that delivered nothing, and an unnamed tab that delivered -+ # measured live on AdomLapper 2026-08-25, where a successful send came back+ # with "title": null).+ #+ # BUT delivered_to ALONE PRODUCES FALSE FAILURES. The API's own hint calls+ # it "a diff, not a guess" that is "empty ... if verification has not caught+ # up", and that is exactly what my first live send hit: an empty array on a+ # prompt that had in fact been typed and submitted. Accept EITHER the+ # transcript diff or the injector's own confirmation.+ inject = d.get("inject") if isinstance(d.get("inject"), dict) else {}+ delivered = (d.get("delivered_to")+ or inject.get("delivered_in_conversation")+ or d.get("delivered_in_conversation"))+ if not delivered:+ hint = d.get("_hints") or d.get("_hint") or res.get("_hints") or ""+ if isinstance(hint, dict):+ hint = " ".join(str(v) for v in hint.values())+ # ok:true with no delivery signal YET is "opened but unconfirmed", which+ # is not the same as failed. Say that precisely.+ if d.get("ok") or res.get("ok"):+ return {"ok": False, "unconfirmed": True,+ "why": "the thread opened and the prompt was submitted, but delivery is "+ "not confirmed yet. Check the new AI tab; the phrase was also "+ "copied so you can paste it if it did not land."}+ return {"ok": False, "why": ("the AI panel opened a thread but did not confirm "+ "delivery. " + (str(hint)[:200] if hint else "")).strip()}+ return {"ok": True, "deliveredTo": str(delivered)[:200],+ "tid": d.get("tid"), "sid": d.get("sid")}+++def ab(target, verb, args=None, timeout=140):+ """One bridge verb. Returns the parsed payload dict (never raises)."""+ cmd = ["adom-bridge", "--ai-thread", AI_THREAD]+ if target and target != "-":+ cmd += ["--target", target]+ cmd += [verb, json.dumps(args or {})]+ try:+ p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)+ d = json.loads(p.stdout or "{}")+ # ab wraps some payloads in output-as-JSON-string; unwrap when it is one+ out = d.get("output")+ if isinstance(out, str) and out.strip().startswith("{"):+ try:+ inner = json.loads(out)+ merged = dict(d)+ merged.update(inner.get("data") if isinstance(inner.get("data"), dict) else inner)+ return merged+ except Exception:+ pass+ return d+ except subprocess.TimeoutExpired:+ return {"error": f"no answer within {timeout}s", "timedOut": True}+ except Exception as e:+ # never leak a raw Python exception (a TimeoutExpired repr used to land+ # verbatim in the Bridge LED as "Command '['adom-bridge', ...]'")+ return {"error": str(e).split("\n")[0][:120]}+++def get_targets():+ try:+ p = subprocess.run(["adom-bridge", "targets"], capture_output=True, text=True, timeout=30)+ d = json.loads(p.stdout or "{}")+ return [t.get("name") for t in (d.get("targets") or []) if t.get("name")]+ except Exception:+ return []+++# ------------------------------------------------------------------- SSE hub+class Hub:+ def __init__(self):+ self._clients = []+ self._lock = threading.Lock()++ def add(self):+ q = queue.Queue(maxsize=64)+ with self._lock:+ self._clients.append(q)+ return q++ def drop(self, q):+ with self._lock:+ if q in self._clients:+ self._clients.remove(q)++ def count(self):+ with self._lock:+ return len(self._clients)++ def broadcast(self, event, data):+ msg = f"event: {event}\ndata: {json.dumps(data)}\n\n"+ with self._lock:+ for q in list(self._clients):+ try:+ q.put_nowait(msg)+ except queue.Full:+ pass+++HUB = Hub()+CONSOLE = []+_console_lock = threading.Lock()+++def log(line):+ with _console_lock:+ CONSOLE.append(f"{time.strftime('%H:%M:%S')} {line}")+ del CONSOLE[:-300]+ print(line, file=sys.stderr, flush=True)+++# ------------------------------------------------------------------ LED model+DEV_MODE = bool(os.environ.get("ALTIUM_DASHBOARD_DEV"))+++def _suite_led():+ """DEV-ONLY (John, 2026-08-18: "the daily suite should not be in here. no+ normal adom user runs that. that's just for me as the main dev"). Shown only+ with --dev / ALTIUM_DASHBOARD_DEV=1: the nightly verb-suite verdict plus+ container-cron liveness (the cron died unnoticed for 4 days once)."""+ try:+ try:+ hb = time.time() - os.path.getmtime("/tmp/cron-heartbeat")+ if hb > 600:+ return {"label": "Daily suite", "state": "err",+ "detail": f"container cron DEAD ({int(hb // 60)} min since heartbeat)",+ "_hint": "The container's cron stopped, so the 6:30am verb suite will not run. "+ "Restart cron in the container and re-check."}+ except OSError:+ pass+ rp = "/home/adom/.altium-verbtest-last-report.json"+ if not os.path.exists(rp):+ rp = "/tmp/altium-verbtest/report.json"+ age_h = (time.time() - os.path.getmtime(rp)) / 3600+ t = (json.load(open(rp)).get("totals")) or {}+ det = f"{t.get('pass', '?')} pass / {t.get('fail', '?')} fail ({int(age_h)}h ago)"+ if age_h > 48:+ return {"label": "Daily suite", "state": "warn", "detail": f"stale: {det}",+ "_hint": "No suite run in over 48h. Check the cron entry and /tmp/altium-verbtest/cron.log."}+ if t.get("fail"):+ return {"label": "Daily suite", "state": "warn", "detail": det,+ "_hint": "Failures are triaged on the wiki issues; the suite files a ticket only for NEW ones."}+ return {"label": "Daily suite", "state": "ok", "detail": det,+ "_hint": "Every verb passed on the last nightly run."}+ except Exception:+ return {"label": "Daily suite", "state": "off", "detail": "no suite report yet",+ "_hint": "The nightly suite writes ~/.altium-verbtest-last-report.json; "+ "it runs at 6:30am when the box is idle."}++++# ── The dock card's ROLLUP (wiki #43, John) ────────────────────────────────+# Without a top-level `state` the dock takes our WORST led, so a yellow+# "update available" turns the whole card yellow while the bridge answers+# perfectly. That is not what a user should read off a card. We own the+# rollup, so we own the judgement about which facets are load-bearing.+#+# I initially declined this, reasoning that a disagreement between our rollup+# and the dock's derivation would be a signal worth seeing. John's argument is+# better: the card is a USER-facing health light, not a debugging surface, and+# an advisory LED must not be able to raise an alarm.+_CRITICAL_LEDS = ("relay", "box", "bridge", "altium", "license") # can this thing work at all+_ADVISORY_LEDS = ("update", "running", "agent") # informational, never alarming+++def _emit_short_states(leds: dict) -> None:+ """THE SERVER emits each pill's short state (#59). The page used to trim+ `detail` to 30 chars, which is what produced the fleet's "in talled and+ pinned" - a client cannot shorten prose it does not understand. Anything+ without a hand-written short label falls back to the STATE word, never to+ a truncated sentence."""+ SHORT = {+ "relay": lambda l: ("connected" if l.get("state") == "ok" else "no boxes"),+ "box": lambda l: ("online" if l.get("state") == "ok" else "offline"),+ "bridge": lambda l: ("answering" if l.get("state") == "ok" else "not answering"),+ "altium": lambda l: (str(l.get("detail") or "").strip()+ if l.get("state") == "ok" else "not installed"),+ "update": lambda l: ("current" if l.get("state") == "ok" else "update ready"),+ "license": lambda l: (str(l.get("detail") or "").strip()[:18]+ if l.get("state") == "ok" else "no licence"),+ "running": lambda l: ("running" if l.get("state") == "ok" else "closed"),+ "agent": lambda l: ("resident" if l.get("state") == "ok" else "dormant"),+ "suite": lambda l: ("passing" if l.get("state") == "ok" else "check it"),+ }+ for key, led in (leds or {}).items():+ if not isinstance(led, dict):+ continue+ try:+ val = SHORT.get(key, lambda l: None)(led)+ except Exception:+ val = None+ if not val:+ val = {"ok": "ok", "err": "broken", "warn": "degraded",+ "busy": "working", "off": "unknown"}.get(led.get("state"), "unknown")+ led["short"] = str(val)[:24]+++def rollup_state(leds: dict) -> dict:+ _emit_short_states(leds)+ """Green when the bridge can actually do its job; yellow only when a+ CRITICAL facet is degraded; red when one is broken."""+ def st(k):+ return (leds.get(k) or {}).get("state")+ crit = [st(k) for k in _CRITICAL_LEDS if st(k)]+ if "err" in crit:+ worst, why = "err", [k for k in _CRITICAL_LEDS if st(k) == "err"]+ elif "warn" in crit:+ worst, why = "warn", [k for k in _CRITICAL_LEDS if st(k) == "warn"]+ else:+ worst, why = "ok", []+ degraded = [k for k in _ADVISORY_LEDS if st(k) in ("warn", "err")]+ return {+ "state": worst,+ "reason": (", ".join(why) if why else+ ("healthy" + (f" (advisory: {', '.join(degraded)})" if degraded else ""))),+ "criticalLeds": list(_CRITICAL_LEDS),+ "advisoryLeds": list(_ADVISORY_LEDS),+ "_hint": ("Top-level rollup for the dock card LED. Advisory facets (an available "+ "update, Altium closed, the resident agent dormant) never turn the card "+ "yellow, because the bridge still works. Only relay/box/bridge/altium/license can."),+ }++def build_status(target):+ """The LED payload. Each facet: {label, state: ok|warn|err|off, detail, _hint}."""+ targets = get_targets()+ if not target or target == "-":+ target = targets[0] if targets else ""+ leds = {}+ box_ok = bool(target and target in targets)+ leds["relay"] = {"label": "Relay", "state": "ok" if targets else "err",+ "detail": f"{len(targets)} box(es) connected" if targets else "no boxes on the relay",+ "_hint": "Boxes connect through adom-bridge. `adom-bridge targets` lists them; "+ "if empty, the desktop app is closed or offline."}+ leds["box"] = {"label": "Box", "state": "ok" if box_ok else "err",+ "detail": target if box_ok else f"'{target or 'none'}' not connected",+ "_hint": "Pick a connected box with the pills above; the bridge runs there, not in this container."}+ if not box_ok:+ for k, lbl in (("bridge", "Bridge"), ("altium", "Altium"), ("license", "Licence"),+ ("update", "Updates"), ("running", "Running"), ("agent", "Agent")):+ leds[k] = {"label": lbl, "state": "off", "detail": "box offline",+ "_hint": "Unknowable while the box is off the relay - dormant, not broken."}+ if DEV_MODE:+ leds["suite"] = _suite_led()+ return {"target": target, "targets": targets, "leds": leds, "settings": {},+ **rollup_state(leds),+ "windows": [], "dashboardVersion": VERSION,+ "generatedAt": time.strftime("%H:%M:%S"),+ "_hint": "Every LED carries its own _hint naming the verb that fixes it. "+ "POST /api/action {action,args} to act; subscribe to /events for live pushes."}++ T = 25 # per-facet ceiling: a box that cannot answer in 25s is "not responding"+ r = ab(target, "altium_readiness", {}, timeout=T)+ if r.get("error"):+ # THE TRUTHFULNESS RULE (ralph iter-3): if the box did not answer, we do+ # not KNOW anything downstream. Saying "Altium not installed" or "up to+ # date" here would be a confident lie built on a failed call.+ leds["box"] = {"label": "Box", "state": "err",+ "detail": f"{target} not responding",+ "_hint": "The box is on the relay but its bridge did not answer in time. "+ "It may be asleep, mid-upgrade, or the desktop app is wedged - "+ "pick another box with the pills, or retry with Refresh."}+ for k, lbl in (("bridge", "Bridge"), ("altium", "Altium"), ("license", "Licence"),+ ("update", "Updates"), ("running", "Running"), ("agent", "Agent")):+ leds[k] = {"label": lbl, "state": "off", "detail": "unknown",+ "_hint": f"Unknowable while {target} is not answering ({r.get('error')})."}+ if DEV_MODE:+ leds["suite"] = _suite_led()+ return {"target": target, "targets": targets, "leds": _apply_activity(leds),+ **rollup_state(_apply_activity(leds)),+ **rollup_state(_apply_activity(leds)),+ "settings": {},+ "windows": [], "dashboardVersion": VERSION,+ "generatedAt": time.strftime("%H:%M:%S"),+ "_hint": "The selected box did not answer; every dependent facet reads unknown "+ "rather than guessing. Switch boxes or retry."}+ installed = bool(r.get("installed"))+ inst = r.get("install") or {}+ ver = r.get("adVersion") or inst.get("fileVersion") or "?"+ leds["bridge"] = {"label": "Bridge", "state": "ok",+ "detail": f"answering (bridge {r.get('bridgeVersion') or '?'})",+ "_hint": "The altium_* verb surface is served by the bridge on the box. If red: "+ "bridge_kill {name:'altium',confirm:true} respawns it from cache."}+ leds["altium"] = {"label": "Altium", "state": "ok" if installed else "err",+ "detail": f"Altium Designer {ver}" if installed else "not installed",+ "_hint": ("Installed at " + str(inst.get("base_dir") or "") + " and driveable.") if installed+ else "No X2.EXE under Program Files\\Altium. The altium-install skill drives "+ "the installer + trial sign-in; the installer elevates, so the last "+ "clicks are the user's."}+ running = bool(r.get("running"))+ leds["running"] = {"label": "Running", "state": "ok" if running else "off",+ "detail": "Altium is open" if running else "Altium is closed",+ "_hint": "altium_launch {} starts it in the background (cold start about 60s). "+ "Every show_* verb launches it on demand, so closed is not a problem."}+ lic = r.get("licenceState") or ""+ if not installed:+ leds["license"] = {"label": "Licence", "state": "off", "detail": "n/a",+ "_hint": "Unknowable until Altium is installed."}+ elif not running:+ leds["license"] = {"label": "Licence", "state": "ok" if r.get("licensed") else "off",+ "detail": lic or "unknown until launched",+ "_hint": "The licence is read from the running instance's title bar; "+ "launch Altium to refresh it."}+ elif r.get("licensed"):+ leds["license"] = {"label": "Licence", "state": "ok", "detail": lic or "licensed",+ "_hint": "Scripting is allowed. altium_license {action:'show'} opens the "+ "License Management page if you need to switch."}+ else:+ leds["license"] = {"label": "Licence", "state": "err", "detail": lic or "No License",+ "_hint": "Altium refuses scripting without a licence. altium_license "+ "{action:'start_trial'} or {action:'use_license'} drives the "+ "License Management page; altium_signin_status checks the account."}+ agent = bool(r.get("agentAlive"))+ leds["agent"] = {"label": "Agent", "state": "ok" if agent else "off",+ "detail": "resident DelphiScript loop answering" if agent+ else ("dormant (auto-starts on the first verb)" if running else "needs Altium"),+ "_hint": "The resident agent is the REPL lane inside Altium (eval, open/show, "+ "library authoring). altium_agent_start {} boots it (about 8s); "+ "altium_agent_recover {} clears a halted engine."}+ cu = ab(target, "bridge_check_updates", {"name": "altium"}, timeout=T)+ row = {}+ for b in (cu.get("bridges") or []):+ if isinstance(b, dict) and b.get("name") == "altium":+ row = b+ if not row and cu.get("name") == "altium":+ row = cu+ if cu.get("error"):+ leds["update"] = {"label": "Updates", "state": "off", "detail": "unknown",+ "_hint": f"bridge_check_updates did not answer ({str(cu.get('error'))[:80]})."}+ elif row.get("updateAvailable"):+ leds["update"] = {"label": "Updates", "state": "warn",+ "detail": f"bridge {row.get('current')} -> {row.get('latest')} on the wiki",+ "_hint": "refresh_bridges {name:'altium'} pulls it; bridge_kill respawns on the new code."}+ else:+ leds["update"] = {"label": "Updates", "state": "ok",+ "detail": f"bridge {r.get('bridgeVersion') or row.get('current') or '?'} is current",+ "_hint": "Adom Bridge re-syncs every bridge from its wiki page every 4h."}+ if DEV_MODE:+ leds["suite"] = _suite_led()++ wins = []+ if running:+ wi = ab(target, "altium_window_info", {}, timeout=T)+ wins = [w.get("title") for w in (wi.get("windows") or []) if w.get("title")]+ return {"target": target, "targets": targets, "leds": _apply_activity(leds),+ **rollup_state(_apply_activity(leds)),+ "settings": {},+ "windows": wins,+ "bridgeVersion": r.get("bridgeVersion"),+ "dashboardVersion": VERSION, "generatedAt": time.strftime("%H:%M:%S"),+ "_hint": "Every LED carries its own _hint naming the verb that fixes it. "+ "POST /api/action {action,args} to act; GET /api/status to re-read; "+ "subscribe to /events (SSE) for live pushes; POST /ui/toast to narrate on the app."}+++# ---------------------------------------------------------------- activity+# John, 2026-08-18: "while you're opening altium show an LED indicator that it's+# opening so the user knows there's background activity rather than just showing+# a dead LED". Every long action registers here, the matching LED switches to a+# pulsing `busy` state with a live label, and a state frame is pushed at once so+# the page reacts immediately instead of waiting for the 15s poll.+_activity: dict = {}+_activity_lock = threading.Lock()+++def set_activity(led, detail, target=None):+ with _activity_lock:+ _activity[led] = {"detail": detail, "t": time.time()}+ _push_state(target)+++def clear_activity(led, target=None):+ with _activity_lock:+ _activity.pop(led, None)+ _push_state(target)+++def _apply_activity(leds):+ with _activity_lock:+ live = dict(_activity)+ for led, info in live.items():+ base = leds.get(led) or {"label": led.title(), "_hint": ""}+ leds[led] = {**base, "state": "busy", "detail": info["detail"],+ "_hint": "Working on it now - this LED is live, not stale. "+ + (base.get("_hint") or "")}+ return leds+++def _push_state(target=None):+ """Recompute and broadcast, so a busy LED appears the instant work starts."""+ def _go():+ try:+ HUB.broadcast("state", cached_status(target or "-", force=True))+ except Exception:+ pass+ threading.Thread(target=_go, daemon=True).start()+++_cache = {"t": 0.0, "target": None, "data": None}+_cache_lock = threading.Lock()+++def cached_status(target, force=False):+ with _cache_lock:+ if (not force and _cache["data"] and _cache["target"] == target+ and time.time() - _cache["t"] < 8):+ return _cache["data"]+ data = build_status(target)+ with _cache_lock:+ _cache.update(t=time.time(), target=target, data=data)+ HUB.broadcast("state", data)+ return data+++_targets_cache = {"t": 0.0, "rows": None}+++def probe_targets():+ """Names first, detail second. This endpoint gates the page's first paint,+ so it must never wait on a wedged box: a 10s per-box ceiling, results cached+ for 30s, and a box that does not answer still appears (just without detail)."""+ if _targets_cache["rows"] and time.time() - _targets_cache["t"] < 30:+ return {"targets": _targets_cache["rows"], "cached": True,+ "_hint": "Cached for 30s; the page paints from names alone."}+ rows = []+ for name in get_targets():+ r = ab(name, "altium_readiness", {}, timeout=10)+ rows.append({"name": name, "altiumRunning": bool(r.get("running")), "windows": 0,+ "altiumVersion": r.get("adVersion"),+ "responding": not bool(r.get("error"))})+ _targets_cache.update(t=time.time(), rows=rows)+ return {"targets": rows,+ "_hint": "Pick where Altium should run. The bridge only drives instances IT launched, "+ "so a Altium the user opened on any box is never parked, bounced, or badged."}+++# -------------------------------------------------------------------- actions+def part_tour(target, args):+ """Not a dashboard button on Altium (yet): the full wiki-part import is a+ creative job (variants, pin-1 checks, 3D binds), so it belongs to an AI+ thread with the say-strip phrase, not a one-click arm that would hide the+ decisions. The strip's 'add this wiki part to my altium library' does it."""+ page = (args or {}).get("page") or "adom/lan7800"+ return {"success": False, "retired": False,+ "error": "Part import is driven by your AI on Altium, not by this button.",+ "_hint": f"Send to AI: 'add {page} to my altium library' - the thread fetches the page, "+ "authors the symbol/footprint/3D body with altium_create_* + altium_add_body, "+ "and proves it with frames."}+++# ---------------------------------------------------------------- surfaces+# The per-part surface ladder, ported from ab's web-control demo+# (adom/adom-bridge web-control-demo.html): ONE real wiki part, eight surfaces,+# each fetching the part's files from its wiki page, installing them into the+# user's real Altium, opening JUST that surface in the background, and rendering+# it from altium_state. The whole circle: wiki component -> the app the EE+# designs in.+PART = {"page": "adom/altium-bridge", "name": "Altium examples", "lib": "Altium examples"}++SURFACES = [+ ("symbol", "Symbol", "altium_show_symbol", None, "schlib",+ "the SpiritLevel example SchLib, one symbol selected in the SCH Library panel"),+ ("footprint", "Footprint", "altium_show_footprint", None, "pcblib",+ "the example PcbLib, one footprint selected in the PCB Library panel"),+ ("3d-chip", "3D chip", "altium_show_3d_chip", None, "pcblib",+ "that footprint in the library editor's 3D mode"),+ ("library", "Library", "altium_show_library", None, "schlib",+ "the library manager panel listing every component"),+ ("schematic", "Schematic", "altium_show_schematic", None, "schdoc",+ "the Bluetooth Sentinel example schematic"),+ ("2d-board", "2D board", "altium_show_2d_board", None, "pcbdoc",+ "the Bluetooth Sentinel board in 2D"),+ ("3d-board", "3D board", "altium_show_3d_board", None, "pcbdoc",+ "the same board in 3D"),+ ("project", "Project", "altium_show_project", None, "prjpcb",+ "the example project open in the Projects panel"),+]++def _altium_hwnd(target):+ """Main Altium window hwnd, for taskbar progress."""+ try:+ wi = ab(target, "altium_window_info", {}, timeout=30)+ for w in (wi.get("windows") or []):+ if w.get("kind") == "main" and w.get("hwnd"):+ return int(w["hwnd"])+ for w in (wi.get("windows") or []):+ if w.get("hwnd"):+ return int(w["hwnd"])+ except Exception:+ pass+ return None+++def taskbar_progress(target, value=None, state="normal", hwnd=None):+ """John, 2026-08-18: "when you automate altium and you know something is going+ to take a while, use ab's progress bar indicator in the windows taskbar".+ Paints on the Altium button itself, so the wait is legible without the user+ watching this page: indeterminate while staging, a real percentage as steps+ land, and cleared (or red) when the job ends."""+ h = hwnd or _altium_hwnd(target)+ if not h:+ return None+ prog = {"state": state}+ if value is not None:+ prog["value"] = int(value)+ ab(target, "desktop_taskbar", {"hwnd": h, "progress": prog}, timeout=30)+ return h+++def _emit(surface, **kw):+ """Broadcast to the page AND record in /console: an AI driving this app reads+ the console back, so surface progress has to land in both places."""+ if kw.get("log"):+ log(f"[{surface}] {kw['log']}")+ HUB.broadcast("surface", {"surface": surface, **kw})+++def ensure_ready(target, surface):+ """A click is a GOAL, not a request for an audit: if Altium is closed,+ launch it in the background and wait - never hand the user an error they+ would fix by pressing a button we could have pressed."""+ r = ab(target, "altium_readiness", {}, timeout=30)+ if r.get("running"):+ return True+ _emit(surface, log="Altium is closed - launching it in the background first", percent=15)+ ab(target, "altium_launch", {}, timeout=180)+ for _ in range(30): # cold start is about 60s, measured+ time.sleep(3)+ r = ab(target, "altium_readiness", {}, timeout=30)+ if r.get("running"):+ _emit(surface, log="Altium is up", percent=25)+ return True+ return False++++def _live_progress_poller(target, surface, stop_ev):+ """While a surface verb runs, relay the BRIDGE's live progress frames+ (altium_progress active[]) onto the card's SSE channel.++ This is the piece John flagged missing (2026-08-21): the card used to show+ only the dashboard's own coarse percent emits (5/40/80), while the bridge+ was publishing real measured frames - stepLabel, etaSec, confidence,+ overdue - that only web control rendered. One poll every 2.5s, read-only,+ and the frames carry `live:true` so the UI can style them (dim non-measured,+ amber overdue/blocked, marquee when percent is null - a guessed number+ presented as a measurement is the failure mode the timing system kills)."""+ while not stop_ev.wait(2.5):+ try:+ r = ab(target, "altium_progress", {"all": True}, timeout=20)+ frames = r.get("active") or []+ if not frames:+ continue+ f = frames[0]+ _emit(surface, live=True,+ percent=f.get("percent"), stepLabel=f.get("stepLabel"),+ etaSec=f.get("etaSec"), confidence=f.get("confidence"),+ overdue=bool(f.get("overdue")), blockedBy=f.get("blockedBy"))+ except Exception:+ pass++def run_surface(target, key):+ """Open this surface in the BACKGROUND on the bridge's shipped example+ files, then show the frame the verb itself returned. Streams progress over+ SSE and onto the Altium taskbar button."""+ spec = next((s for s in SURFACES if s[0] == key), None)+ if key == "state":+ spec = ("state", "Altium state", "altium_state", None, None,+ "captures every Altium window as a screenshot")+ if not spec:+ _emit(key, status="error", log=f"unknown surface {key}")+ return+ _key, label, verb, _need, pick, note = spec+ hwnd = None+ try:+ _emit(key, status="running", log=note, percent=5)+ set_activity("running", f"opening the {label.lower()} in the background...", target)+ hwnd = taskbar_progress(target, None, "indeterminate")+ if not ensure_ready(target, key):+ raise RuntimeError("Altium did not come up within 90s - try Launch Altium, "+ "or check the Altium and Licence LEDs")+ taskbar_progress(target, 60, "normal", hwnd)+ _emit(key, log=f"opening the {label.lower()} in the background", percent=60)+ args = {"timeout": 120} if key != "state" else {"screenshots": True}+ _stop = threading.Event()+ threading.Thread(target=_live_progress_poller, args=(target, key, _stop),+ daemon=True).start()+ try:+ r = ab(target, verb, args, timeout=300)+ finally:+ _stop.set()+ if r.get("success") is False:+ code = r.get("errorCode")+ if code == "blocked_by_dialog":+ raise RuntimeError("a dialog is up on Altium's main window - "+ "altium_check_dialogs shows it, altium_dismiss_dialogs clears it")+ if code == "not_licensed":+ raise RuntimeError("Altium is running without a licence - see the Licence LED")+ raise RuntimeError(str(r.get("error") or code)[:160])+ shot, title = None, None+ sh = r.get("shot") or {}+ if isinstance(sh, dict) and sh.get("image"):+ shot, title = sh.get("image"), sh.get("title") or r.get("title")+ if not shot and key == "state":+ shots = r.get("shots") or []+ if shots:+ shot, title = shots[0].get("image"), shots[0].get("title")+ if shot and str(shot).startswith("data:"):+ shot = shot.split(",", 1)[1]+ if not shot:+ _emit(key, log="rendering it from altium_state", percent=92)+ shot, title = _render(target, pick)+ taskbar_progress(target, 100, "none", hwnd)+ if not shot:+ _emit(key, status="error", percent=100,+ log=f"{label} opened, but no window could be captured to render it")+ return+ _emit(key, status="done", percent=100, image=shot, title=title,+ log=f"{label} shown in the background" + (f" - {title}" if title else ""))+ except Exception as e:+ if hwnd:+ taskbar_progress(target, 100, "error", hwnd)+ _emit(key, status="error", log=str(e)[:200])+ finally:+ clear_activity("running", target)+++def _render(target, pick):+ """Pull this surface's window out of altium_state as a base64 PNG.++ ralph iter-5: the captures live in state.SHOTS (each {title, image, hwnd,+ path}), NOT in state.windows - windows carries geometry only, so the render+ always came back empty and the card had nothing to show. `includeImages` is+ what produces shots at all."""+ st = ab(target, "altium_state", {"screenshots": True}, timeout=240)+ shots = st.get("shots") or []+ # EVIDENCE MUST BE THE THING THAT WAS ASKED FOR (John, 2026-08-19: "i had+ # asked for a footprint and the evidence you provided was just of the project+ # window"). The old fallback grabbed the last window on screen when the+ # expected one was missing, which turns a failure into a screenshot of+ # something else entirely - worse than no evidence, because it looks like+ # proof. If the requested surface is not there, say so.+ if pick:+ chosen = next((w for w in shots if pick in (w.get("title") or "").lower()), None)+ if not chosen:+ seen = ", ".join((w.get("title") or "?")[:40] for w in shots) or "nothing"+ raise RuntimeError(f"the {pick} window never appeared - on screen instead: {seen}")+ else:+ chosen = shots[-1] if shots else None+ if not chosen:+ return None, None+ img = (chosen.get("image") or chosen.get("screenshot")+ or chosen.get("imageBase64") or chosen.get("png"))+ return img, chosen.get("title")+++def _launch_watched(target):+ """Launch with a live LED: 'launching...' then 'waiting for the licence and+ the resident agent...' then back to the real status once it is up."""+ try:+ set_activity("running", "launching Altium in the background (about 60s)...", target)+ r = ab(target, "altium_launch", {}, timeout=240)+ for _ in range(30):+ time.sleep(3)+ rd = ab(target, "altium_readiness", {}, timeout=25)+ if rd.get("running"):+ break+ set_activity("agent", "Altium is up - booting the resident agent...", target)+ ab(target, "altium_agent_start", {"waitSec": 40}, timeout=90)+ return r+ finally:+ clear_activity("running", target)+ clear_activity("agent", target)+++# ------------------------------------------------------------------- demo+# altium_demo is one beat per call ({stage}); the dashboard runs the loop so+# the page gets a real progress bar and one screenshot per finished beat.+_demo = {"running": False, "percent": 0, "stepLabel": "", "beatIndex": 0, "beatCount": 6,+ "done": False, "error": None, "shots": [], "paused": False, "stop": False}+_demo_lock = threading.Lock()+++def _demo_run(target):+ stage = 0+ with _demo_lock:+ _demo.update(running=True, percent=0, stepLabel="starting", beatIndex=0,+ done=False, error=None, shots=[], paused=False, stop=False)+ try:+ hwnd = taskbar_progress(target, None, "indeterminate")+ while True:+ while _demo["paused"] and not _demo["stop"]:+ time.sleep(1)+ if _demo["stop"]:+ break+ r = ab(target, "altium_demo", {"stage": stage}, timeout=240)+ if r.get("done"):+ break+ if r.get("success") is False:+ raise RuntimeError(str(r.get("error") or r.get("errorCode") or "beat failed")[:160])+ label = r.get("label") or f"beat {stage + 1}"+ shots = r.get("screenshots") or []+ with _demo_lock:+ _demo["beatIndex"] = stage + 1+ _demo["stepLabel"] = label+ _demo["percent"] = int(100 * (stage + 1) / max(1, _demo["beatCount"]))+ for sh in shots:+ if sh.get("image"):+ _demo["shots"].append({"image": sh["image"], "title": label, "step": label})+ taskbar_progress(target, _demo["percent"], "normal", hwnd)+ stage += 1+ taskbar_progress(target, 100, "none", hwnd)+ with _demo_lock:+ _demo.update(done=True, percent=100, stepLabel="complete" if not _demo["stop"] else "stopped",+ running=False)+ except Exception as e:+ with _demo_lock:+ _demo.update(done=True, error=str(e)[:200], running=False)+++def do_action(target, name, args):+ args = args or {}+ if name == "launch":+ threading.Thread(target=_launch_watched, args=(target,), daemon=True).start()+ return {"success": True, "started": "launch",+ "_hint": "Launching in the background; the Running and Agent LEDs show live "+ "progress and the page updates over /events as it happens."}+ if name == "close":+ set_activity("running", "closing Altium...", target)+ try:+ return ab(target, "altium_close", {}, timeout=180)+ finally:+ clear_activity("running", target)+ if name in ("setup", "install_plugin", "agent_start"):+ set_activity("agent", "booting the resident DelphiScript agent...", target)+ try:+ return ab(target, "altium_agent_start", {"waitSec": 40}, timeout=90)+ finally:+ clear_activity("agent", target)+ if name == "demo":+ if _demo.get("running"):+ return {"success": False, "error": "a demo is already running",+ "_hint": "Poll demo_progress, or demo_control {cmd:'stop'} first."}+ threading.Thread(target=_demo_run, args=(target,), daemon=True).start()+ return {"success": True, "started": "demo",+ "_hint": "Six beats in the background; poll demo_progress every few seconds."}+ if name == "demo_progress":+ with _demo_lock:+ snap = {k: _demo[k] for k in ("percent", "stepLabel", "beatIndex", "beatCount", "done", "error")}+ snap["stepShot"] = _demo["shots"].pop(0) if _demo["shots"] else None+ if _demo.get("paused"):+ snap["stepLabel"] = "Paused"+ return snap+ if name == "demo_control":+ cmd = str(args.get("cmd") or "")+ if cmd == "pause":+ _demo["paused"] = True+ elif cmd == "play":+ _demo["paused"] = False+ elif cmd == "stop":+ _demo["stop"] = True+ _demo["paused"] = False+ else:+ return {"error": f"unknown demo command {cmd}"}+ return {"success": True, "cmd": cmd}+ if name == "upgrade":+ set_activity("update", "pulling the latest altium bridge from the wiki...", target)+ try:+ return ab(target, "refresh_bridges", {"name": "altium"}, timeout=300)+ finally:+ clear_activity("update", target)+ if name in ("set_overlay_badges", "set_activity_indicator", "restore_settings"):+ return {"success": False, "error": "the altium bridge has no dashboard-side settings yet",+ "_hint": "Taskbar badge + progress are always on (handlers/taskbar.py); nothing to toggle."}+ if name == "say":+ return {"success": True, "phrases": say_phrases()}+ if name == "try_phrase":+ return {"success": False, "retired": True,+ "error": "try_phrase is retired. Use Send to AI (POST /api/say-send).",+ "_hint": "Resuming or typing into EXISTING threads remains parked."}+ if name == "part_tour":+ return part_tour(target, args)+ if name == "surface":+ key = (args.get("surface") or "").strip()+ threading.Thread(target=run_surface, args=(target, key), daemon=True,+ name=f"surface-{key}").start()+ return {"success": True, "started": key,+ "_hint": "Progress streams on /events as `surface` events (status, log, "+ "percent, image) and on the Altium taskbar button's progress bar."}+ if name == "surfaces":+ return {"success": True, "part": PART,+ "surfaces": [{"key": k, "label": lbl, "note": note}+ for k, lbl, _v, _n, _p, note in SURFACES]}+ return {"error": f"unknown action {name}",+ "_hint": "Actions: launch, close, setup, demo, demo_progress, demo_control, upgrade, surface, surfaces."}+++# --------------------------------------------------------------------- server+class Handler(BaseHTTPRequestHandler):+ protocol_version = "HTTP/1.1"+ server_version = "altium-dashboard/" + VERSION++ def log_message(self, *a):+ pass++ def _send(self, code, ctype, body, extra=None):+ if isinstance(body, str):+ body = body.encode("utf-8")+ self.send_response(code)+ self.send_header("Content-Type", ctype)+ self.send_header("Content-Length", str(len(body)))+ self.send_header("Access-Control-Allow-Origin", "*")+ for k, v in (extra or {}).items():+ self.send_header(k, v)+ self.end_headers()+ self.wfile.write(body)++ def _json(self, obj, code=200):+ self._send(code, "application/json", json.dumps(obj))++ def _read_body(self):+ try:+ n = int(self.headers.get("Content-Length") or 0)+ return json.loads(self.rfile.read(n) or b"{}") if n else {}+ except Exception:+ return {}++ def do_GET(self):+ path = urlparse(self.path).path.rstrip("/") or "/"+ if path.endswith("/newbie-panel.js"):+ # The ONE shared panel implementation (adom-ui-design 2.0.0). Served+ # verbatim, never forked: fixes land in the pack, not here.+ try:+ return self._send(200, "application/javascript; charset=utf-8",+ (UI_DIR / "newbie-panel.js").read_bytes(),+ {"Cache-Control": "no-store"})+ except Exception as e:+ return self._json({"error": f"module missing: {e}"}, 500)+ if path.endswith(("/favicon.svg", "/favicon.png", "/favicon.ico",+ "/dock-altium.svg", "/altium-logo.png")):+ # endswith: the adom.cloud proxy mounts the app under a sub-path.+ try:+ return self._send(200, "image/svg+xml", LOGO_PATH.read_bytes())+ except Exception:+ return self._json({"error": "logo missing"}, 404)+ if path == "/health":+ # pid published so a SUCCESSOR can retire us directly instead of+ # inferring ownership. Older builds do not send it, which is why+ # _pid_owning_port() exists and does not depend on this.+ return self._json({"ok": True, "app": "altium-dashboard", "version": VERSION,+ "pid": os.getpid(), "sseClients": HUB.count()})+ am = re.search(r"/assets/(.+)$", path)+ if am:+ # Settings screenshots + the shared Adom cog. Real captures only+ # (settings-layout skill); anything else under here 404s. Matched+ # anywhere in the path because the adom.cloud proxy mounts the+ # app under a sub-path.+ rel = am.group(1)+ f = (UI_DIR / "assets" / rel).resolve()+ if not str(f).startswith(str((UI_DIR / "assets").resolve())) or not f.is_file():+ return self._json({"error": "no such asset"}, 404)+ mime = {"svg": "image/svg+xml", "png": "image/png", "jpg": "image/jpeg",+ "webp": "image/webp"}.get(f.suffix.lstrip(".").lower(),+ "application/octet-stream")+ return self._send(200, mime, f.read_bytes(),+ {"Cache-Control": "no-cache"})+ if path.endswith("/updater/status"):+ # LOST IN AN EARLIER REBUILD, which meant the module's version+ # gate silently no-opped: its gate() catches a failure and+ # returns open, so a missing proxy reads as "allowed".+ return _proxy_control(self, "/updater/status", timeout=8)+ if path.endswith("/api/viewport"):+ if "matrix" in (urlparse(self.path).query or ""):+ return self._json({"reports": sorted(VIEWPORT_LOG,+ key=lambda r: r.get("innerWidth") or 0)})+ return self._json(LAST_VIEWPORT or {"note": "no page has reported a viewport yet"})+ if path == "/console":+ with _console_lock:+ return self._json({"lines": list(CONSOLE)})+ if path == "/events":+ return self._sse()+ if path == "/api/targets":+ return self._json(probe_targets())+ m = re.match(r"^/api/verb_times(?:/([^/]+))?$", path)+ if m:+ # The verb-times panel (fusion parity). Proxied per target so the+ # numbers are THAT box's measured history, not this container's.+ t = unquote(m.group(1) or "") or (get_targets() or ["-"])[0]+ r = ab(t, "altium_verb_times", {}, timeout=30)+ return self._json({"target": t, "verbs": r.get("verbs") or {},+ "error": r.get("error") or None})+ m = re.match(r"^/(?:api/status|state)(?:/([^/]+))?(/force)?$", path)+ if m:+ target = unquote(m.group(1) or "-")+ force = bool(m.group(2))+ log(f"status target={target}{' force' if force else ''}")+ return self._json(cached_status(target, force))+ if not _wants_app_shell(self, path):+ # NOT a catch-all any more. An unmatched asset, an unknown route in a+ # namespace we own, or any non-browser client gets a real 404, so the+ # launch-console 404 watch can actually fire on a virgin install.+ return self._json({"error": "not found", "path": path}, 404)+ try: # a real browser navigation renders the app (proxy sub-paths still work)+ # no-store: the dock's Hard Refresh Open must never get a cached+ # shell after a version bump (dockbar guide).+ # Inject the phrase list at SERVE time. It comes from dockbar.json,+ # which is static config, so making the panel wait on an /api round+ # trip only created a boot race: on first paint the target is not+ # resolved yet, the call came back empty, and the page rendered NO+ # panel at all - the same "panel with no prompts" shape the fleet+ # audit kept finding elsewhere. Static data belongs in the document.+ html = (UI_DIR / "index.html").read_text(encoding="utf-8")+ html = html.replace("</head>",+ "<script>window.__SAY_PHRASES__=" ++ json.dumps(say_phrases()) + ";</script></head>", 1)+ return self._send(200, "text/html; charset=utf-8", html,+ {"Cache-Control": "no-store, must-revalidate"})+ except Exception as e:+ return self._json({"error": f"ui missing: {e}"}, 500)++ def do_OPTIONS(self):+ # CORS preflight on the routes we ACTUALLY serve (dockbar guide): the dock+ # card and the webview both call cross-origin. This used to answer 204 for+ # every path, which made the module's checkSend "send route wired" row+ # green for routes that do not exist - it probes with OPTIONS, so a blanket+ # preflight answered the probe instead of the route.+ path = urlparse(self.path).path.rstrip("/") or "/"+ if not _route_exists(path):+ return self._json({"error": "not found", "path": path}, 404)+ self.send_response(204)+ self.send_header("Access-Control-Allow-Origin", "*")+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")+ self.send_header("Access-Control-Allow-Headers", "Content-Type")+ self.send_header("Access-Control-Max-Age", "86400")+ self.end_headers()++ def do_POST(self):+ path = urlparse(self.path).path.rstrip("/") or "/"+ body = self._read_body()+ if path == "/api/action":+ name = body.get("action") or ""+ target = body.get("target") or (get_targets() or ["-"])[0]+ log(f"action {name} target={target}")+ return self._json(do_action(target, name, body.get("args")))+ if path == "/ui/toast":+ msg, kind = body.get("message") or "", body.get("kind") or "info"+ log(f"toast [{kind}] {msg}")+ HUB.broadcast("toast", {"message": msg, "kind": kind})+ return self._json({"ok": True, "delivered": HUB.count(),+ "_hint": "delivered = live SSE viewers that showed it. "+ "0 means nobody has the page open - open it first."})+ if path.endswith("/api/viewport"):+ # The page reports the width of the surface it actually rendered in.+ # Assumption is how the 760px breakpoint bug survived: John's dock+ # pane is 744px and nobody's test matrix included it.+ global LAST_VIEWPORT+ LAST_VIEWPORT = dict(body or {})+ LAST_VIEWPORT["at"] = time.strftime("%H:%M:%S")+ VIEWPORT_LOG.append(dict(LAST_VIEWPORT))+ del VIEWPORT_LOG[:-16]+ log(f"viewport {LAST_VIEWPORT.get('innerWidth')}px cols={LAST_VIEWPORT.get('panelCols')}")+ return self._json({"ok": True})+ if path.endswith("/ai-threads/run-prompt"):+ return _proxy_control(self, "/ai-threads/run-prompt", body=body or {}, timeout=125)+ if path.endswith("/api/say-send"):+ phrase = str((body or {}).get("phrase") or "").strip()+ if not phrase:+ return self._json({"ok": False, "why": "phrase required"}, 400)+ log(f"say-send: {phrase[:60]}")+ return self._json(say_send(phrase))+ if path == "/ui/click":+ t = body.get("target") or ""+ log(f"ui click {t}")+ HUB.broadcast("click", {"target": t})+ return self._json({"ok": True, "delivered": HUB.count(),+ "_hint": "Targets: setup, launch, close, demo, part-tour, settings, refresh. "+ "The PAGE performs the click, so what the AI drives is what the user gets."})+ return self._json({"error": "not found",+ "_hint": "POST /api/action {action,args}, /ui/toast {message,kind}, /ui/click {target}"}, 404)++ def _sse(self):+ """Headers immediately, then one flush PER EVENT. The 2KB comment padding+ plus no-transform / X-Accel-Buffering defeat proxy buffering (the adom.cloud+ proxy otherwise holds every byte until the stream ends)."""+ self.send_response(200)+ self.send_header("Content-Type", "text/event-stream")+ self.send_header("Cache-Control", "no-cache, no-transform")+ self.send_header("X-Accel-Buffering", "no")+ self.send_header("Access-Control-Allow-Origin", "*")+ self.send_header("Connection", "keep-alive")+ self.end_headers()+ q = HUB.add()+ log(f"sse client connected ({HUB.count()} live)")+ try:+ self.wfile.write((": " + "p" * 2048 + "\n\n").encode())+ self.wfile.write(f"event: hello\ndata: {json.dumps({'version': VERSION})}\n\n".encode())+ self.wfile.flush()+ while True:+ try:+ msg = q.get(timeout=15)+ except queue.Empty:+ msg = ": ping\n\n"+ self.wfile.write(msg.encode())+ self.wfile.flush()+ except Exception:+ pass+ finally:+ HUB.drop(q)+ log(f"sse client disconnected ({HUB.count()} live)")+++# ------------------------------------------------------------------------ CLI++def claim_dock_webview(port):+ """Take ownership of the dock's pre-opened webview tab (dock contract,+ 2026-08-23, blessed as THE model: the webview belongs to the CLI).++ The dock sets ADOM_DOCK_WEBVIEW to the tab name it opened for this app.+ open-or-refresh with the same --name is idempotent - re-running it just+ navigates the existing tab, which is the ownership handle: we renavigate+ after restarts and repaint on state changes. Unset env (manual serve) or+ a failed call are both silent no-ops; the dock's own interstitial+ navigation remains the fallback either way."""+ name = os.environ.get("ADOM_DOCK_WEBVIEW") or ""+ if not name:+ return+ try:+ subprocess.run(["adom-cli", "hydrogen", "webview", "open-or-refresh",+ "--name", name, "--url", direct_url(port)],+ capture_output=True, timeout=20)+ log(f"claimed dock webview tab '{name}' -> {direct_url(port)}")+ except Exception:+ pass++def direct_url(port):+ """What `serve --print-url` prints: the DIRECT port, always.++ Dock-bar thread request (2026-08-23): the consumer of --print-url is the+ Hydrogen dock, whose workspace shares the port namespace (WSL2 mirrored+ networking locally; container-side probe in cloud), so the /proxy/<port>/+ hop is pure indirection - and its prefix is what caused the dock's+ health-URL join bugs. 127.0.0.1 rather than the NAME localhost, because+ Windows resolves localhost IPv6-first while WSL binds IPv4 only: measured+ ~820ms per request vs 13ms, most of the dock's 2000ms probe budget."""+ return f"http://127.0.0.1:{port}/"+++def proxy_url(port):+ tpl = os.environ.get("VSCODE_PROXY_URI") or ""+ if "{{port}}" in tpl:+ return tpl.replace("{{port}}", str(port))+ return f"http://127.0.0.1:{port}/"+++def local_call(port, path, payload=None):+ import urllib.request+ url = f"http://127.0.0.1:{port}{path}"+ data = json.dumps(payload).encode() if payload is not None else None+ req = urllib.request.Request(url, data=data, method="POST" if data else "GET",+ headers={"Content-Type": "application/json"})+ try:+ with urllib.request.urlopen(req, timeout=20) as r:+ return json.loads(r.read().decode())+ except Exception as e:+ return {"error": str(e),+ "_hint": f"Is the dashboard serving? `altium-dashboard serve --port {port}` (health: /health)."}+++def main():+ argv = sys.argv[1:] or ["serve"]+ cmd = argv[0]++ def flag(name, default=None):+ return argv[argv.index(name) + 1] if name in argv and argv.index(name) + 1 < len(argv) else default++ port = int(flag("--port", DEFAULT_PORT))+ target = flag("--target", "")+ if "--dev" in argv: # maintainer view: adds the nightly-suite / cron LED+ globals()["DEV_MODE"] = True++ if cmd == "serve":+ # IDEMPOTENT SERVE (wiki #43, John). Hydrogen runs the serve step on+ # EVERY launch - `skip_if` guards the install step, not this one. So the+ # second click of a session used to run serve against our own live+ # instance and die on EADDRINUSE with a raw traceback in the launch+ # console. An already-running dashboard should make relaunch INSTANT,+ # not fatal, so: if a healthy instance of OURS already owns the port,+ # print its URL and exit 0.+ def _running_version(p_):+ """Version of OUR instance on the port, or None if none/not ours."""+ import urllib.request # imported per-use in this module+ try:+ with urllib.request.urlopen(+ f"http://127.0.0.1:{p_}/health", timeout=1.5) as r:+ d_ = json.loads(r.read().decode("utf-8") or "{}")+ # Confirm it is OURS - another service on the port is not a+ # reason to claim success.+ if bool(d_.get("ok")) and d_.get("app") == "altium-dashboard":+ return str(d_.get("version") or "?")+ except Exception:+ pass+ return None++ def _healthy_instance(p_):+ return _running_version(p_) is not None++ def _pid_owning_port(p_):+ """The pid LISTENING on p_, via /proc/net/tcp inode -> /proc/*/fd.++ PORT OWNERSHIP, not argv (dockbar guide: "argv is a claim, not a+ fact"). Rehearsed against a real predecessor 2026-08-25: selecting+ victims by argv alone killed an UNRELATED instance serving a+ different port, because every instance of this tool has identical+ argv. The port is the thing being contested, so the port is what+ decides who is retired."""+ want = []+ for fam, path in ((4, "/proc/net/tcp"), (6, "/proc/net/tcp6")):+ try:+ for line in open(path).read().splitlines()[1:]:+ f = line.split()+ if len(f) < 4 or f[3] != "0A": # 0A = LISTEN+ continue+ if int(f[1].split(":")[1], 16) == int(p_):+ want.append(f[9]) # inode+ except Exception:+ pass+ if not want:+ return None+ me = os.getpid()+ for pid in os.listdir("/proc"):+ if not pid.isdigit() or int(pid) == me:+ continue+ try:+ for fd in os.listdir(f"/proc/{pid}/fd"):+ try:+ link = os.readlink(f"/proc/{pid}/fd/{fd}")+ except Exception:+ continue+ if link.startswith("socket:[") and link[8:-1] in want:+ return int(pid)+ except Exception:+ continue+ return None++ def _retire_stale(p_, their):+ """A healthy instance of a DIFFERENT version is not a reason to skip+ serving; it is the thing to replace (dock thread, 2026-08-23).++ Retire the process that OWNS THE PORT, and use argv only as a+ SAFETY FILTER on that one pid - never as the selector. Selecting by+ argv killed a bystander on another port in rehearsal."""+ log(f"altium-dashboard {their} is serving on {p_} but this is {VERSION}; "+ f"retiring the stale instance")+ victim = _pid_owning_port(p_)+ if victim is None:+ log(f"could not identify the process owning port {p_}; not killing anything")+ return False+ try:+ argv_ = open(f"/proc/{victim}/cmdline", "rb").read().split(b"\0")+ except Exception:+ argv_ = []+ ours = (len(argv_) >= 3+ and os.path.basename(argv_[0]).startswith(b"python")+ and argv_[1].endswith(b"altium_dashboard.py")+ and argv_[2] == b"serve")+ if not ours:+ log(f"port {p_} is held by pid {victim} which is not one of ours; refusing to kill")+ return False+ import signal+ try:+ os.kill(victim, signal.SIGTERM)+ except Exception:+ pass+ for _ in range(40): # up to ~4s for the port to free+ if _running_version(p_) is None:+ return True+ time.sleep(0.1)+ try:+ os.kill(victim, signal.SIGKILL)+ except Exception:+ pass+ time.sleep(0.5)+ return _running_version(p_) is None++ their = _running_version(port)+ if their is not None and their != VERSION:+ if not _retire_stale(port, their):+ print(f"altium-dashboard: a stale {their} instance owns port {port} and "+ f"would not exit; kill it and relaunch.", file=sys.stderr, flush=True)+ raise SystemExit(2)+ their = None+ if their is not None:+ if "--print-url" in argv:+ print(direct_url(port), flush=True)+ else:+ log(f"altium-dashboard {VERSION} already serving on {port}; nothing to do")+ return++ # BIND FIRST. The old order printed the URL and only then tried to bind,+ # so a port clash advertised a URL nothing was listening on (wiki #43).+ try:+ srv = ThreadingHTTPServer(("0.0.0.0", port), Handler)+ except OSError as e:+ # Lost a race, or something else owns the port. Re-probe once: if it+ # is us after all, this launch is still a success.+ if _healthy_instance(port):+ if "--print-url" in argv:+ print(direct_url(port), flush=True)+ return+ # A readable one-liner beats a traceback in the launch console.+ print(f"altium-dashboard: cannot serve on port {port}: {e}. "+ f"Something else owns it - pick another with --port.",+ file=sys.stderr, flush=True)+ raise SystemExit(2)+ srv.daemon_threads = True++ if "--print-url" in argv:+ # The Hydrogen dock contract (adom/hydrogen-dock-sdk): print the URL+ # and SELF-BACKGROUND, so the workspace exec returns promptly.+ # Hydrogen deliberately does not detach for us - it runs this step+ # synchronously, and a serve that stays in the foreground holds the+ # exec for its whole timeout, after which the process tree is KILLED+ # and the readiness probe finds nothing listening. That is the+ # "Waiting to answer" hang John measured on the Fusion button.+ #+ # The socket is already bound above, so by the time the parent+ # prints, the URL is genuinely reachable.+ url = direct_url(port)+ if hasattr(os, "fork"):+ if os.fork() > 0:+ print(url, flush=True)+ os._exit(0) # _exit: never run atexit / close the fd+ os.setsid()+ # Detach stdio or the exec's pipe stays open and it keeps waiting+ # on a command that has, as far as it can tell, not finished.+ _null = os.open(os.devnull, os.O_RDWR)+ for _fd in (0, 1, 2):+ try:+ os.dup2(_null, _fd)+ except Exception:+ pass+ claim_dock_webview(port) # socket already bound above+ else:+ # No fork (Windows): print and serve in the foreground. Honest+ # fallback - the dock runs this in a Linux container.+ print(url, flush=True)+ claim_dock_webview(port)+ log(f"altium-dashboard {VERSION} serving on {port} (target={target or 'auto'})")+ try:+ srv.serve_forever()+ except KeyboardInterrupt:+ pass+ return++ if cmd == "status":+ print(json.dumps(build_status(target or "-"), indent=2))+ return++ if cmd == "health":+ print(json.dumps(local_call(port, "/health")))+ return++ if cmd == "ui":+ sub = argv[1] if len(argv) > 1 else ""+ if sub == "toast":+ print(json.dumps(local_call(port, "/ui/toast",+ {"message": argv[2] if len(argv) > 2 else "",+ "kind": flag("--kind", "info")})))+ elif sub == "click":+ print(json.dumps(local_call(port, "/ui/click",+ {"target": argv[2] if len(argv) > 2 else ""})))+ elif sub == "state":+ print(json.dumps(local_call(port, "/state"), indent=2))+ elif sub == "console":+ print(json.dumps(local_call(port, "/console"), indent=2))+ else:+ print(json.dumps({"error": "ui <toast|click|state|console>"}))+ return++ if cmd == "open":+ url = proxy_url(port)+ box = target or (get_targets() or [""])[0]+ if "--webview" in argv:+ print(json.dumps({"url": url, "surface": "webview",+ "_hint": "Open this URL in a Hydrogen webview panel (the `webview` skill); "+ "panel placement stays the AI's decision."}))+ return+ r = ab(box, "pup_open_window", {"sessionId": "altium-dashboard", "url": url}, timeout=120)+ ab(box, "pup_alert_window", {"sessionId": "altium-dashboard"}, timeout=60)+ print(json.dumps({"url": url, "surface": "pup", "target": box, "opened": r.get("success"),+ "_hint": "Opened in a BACKGROUND pup window and flashed its taskbar button. "+ "Never raise it yourself - let the user click it."}))+ return++ print(json.dumps({"error": f"unknown command {cmd}",+ "_hint": "serve | status | ui <toast|click|state|console> | open | health"}))+++if __name__ == "__main__":+ main()
dashboard/ui/altium-logo.pngadded⋯ 1 unchanged line ⋯
dashboard/ui/dock-altium.svgadded+1@@ -0,0 +1 @@+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64" viewBox="0 0 64 64"><image width="64" height="64" xlink:href="data:image/png;base64,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"/></svg>
dashboard/ui/index.htmladded+1003@@ -0,0 +1,1003 @@+<!doctype html>+<html lang="en">+<head>+<meta charset="utf-8">+<meta name="viewport" content="width=device-width, initial-scale=1">+<title>Altium Dashboard</title>+<link rel="icon" type="image/svg+xml" href="favicon.svg">+<style>+:root{+ --bg:#0d1117;--surface:#161b22;--elevated:#1c2128;--overlay:#21262d;--border:#30363d;+ --text:#e6edf3;--text-body:#c9d1d9;--text-2:#8b949e;+ --accent:#00b8b1;--accent-hover:#00d4cb;--on-accent:#05221f;--green:#3fb950;--red:#f85149;--yellow:#d29922;--blue:#64abff;+ --brand-heading:'Familjen Grotesk',sans-serif;--brand-body:'Satoshi',sans-serif;+}+*{box-sizing:border-box;margin:0;padding:0}+@font-face{font-family:'Familjen Grotesk';src:url('https://adom.inc/fonts/FamiljenGrotesk-SemiBold.woff2') format('woff2');font-weight:600}+@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/Satoshi-Regular.woff2') format('woff2');font-weight:400}+@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/Satoshi-Medium.woff2') format('woff2');font-weight:500}+body{background:var(--bg);color:var(--text-body);font:15px/1.5 'Satoshi',sans-serif;min-height:100vh}+/* House scrollbar everywhere (scrollbars sub-skill, applied verbatim per the+ dock thread 2026-08-23: match the wiki's slim dark pill on every surface). */+*{scrollbar-width:thin;scrollbar-color:rgba(139,148,158,.38) transparent}+::-webkit-scrollbar{width:10px;height:10px}+::-webkit-scrollbar-track{background:transparent}+::-webkit-scrollbar-thumb{background:rgba(139,148,158,.32);border-radius:999px;border:2px solid transparent;background-clip:padding-box}+::-webkit-scrollbar-thumb:hover{background:rgba(0,184,177,.55);background-clip:padding-box}+::-webkit-scrollbar-corner{background:transparent}+body{overflow-x:hidden} /* the page never scrolls horizontally */+/* FLUID, and the FLEET GUTTER. No max-width: a dashboard fills its window like+ a native app. The 16px horizontal padding is the contract (newbie-panel+ 2.3.0): the panel's border box must equal documentElement.clientWidth - 32,+ so every app's panel is the same width at the same pane and the 700px+ two-column threshold is crossed by all of them together. This rule was lost+ in an earlier CSS deletion, which left the panel full-bleed at 734 and the+ page content hugging the left edge - visible in my own frame, and I did not+ look hard enough at it. */+main{margin:0;padding:20px 16px 84px}+header{height:44px;display:flex;align-items:center;gap:12px;padding:0 16px;margin:0;border-radius:0;background:var(--surface);border-bottom:1px solid var(--border);overflow:hidden}+/* The header was the ONLY thing overflowing narrow panes: measured at a real+ 236px surface the nowrap brand + target chip + four ghost buttons pushed+ scrollWidth to 410. Everything below the header already fitted. So the bar+ shrinks: brand ellipsises, the chip caps and ellipsises, the buttons never+ shrink (they are the controls), and the subtitle drops out first. */+.brand{min-width:0;overflow:hidden;text-overflow:ellipsis}+.brand svg{flex:none}+.actions{flex:none}+.target-chip{min-width:0;max-width:38vw;overflow:hidden}+.target-chip #chipName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}+/* Header responsiveness (the ONLY overflow source at narrow panes: the+ nowrap brand + chip + buttons pushed a 236px surface to 410px). The module+ owns the panel; this is ours. */+@media (max-width:700px){+ .brand .sub{display:none}+ .target-chip{max-width:30vw}+}+@media (max-width:420px){+ header{gap:8px;padding:0 10px}+ .vsep{display:none}+ .target-chip{max-width:26vw}+}+/* status-leds pill row */+.leds{display:flex;flex-wrap:wrap;gap:8px}+.led-pill{display:inline-flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;color:var(--text-body);transition:border-color .15s ease}+.led-pill:hover{border-color:var(--teal)}+.led-pill:active{transform:translateY(1px)}+.led-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}+.led-pill[data-state=ok] .dot{background:var(--led-green)}+.led-pill[data-state=busy] .dot{background:var(--led-amber);animation:ledpulse 1.1s ease-in-out infinite}+.led-pill[data-state=stuck] .dot{background:var(--led-amber)}+.led-pill[data-state=bad] .dot{background:var(--led-red)}+.led-pill .st{color:var(--muted)}+.led-detail-pop{position:fixed;z-index:99998;max-width:360px;background:var(--overlay);border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4)}+.led-detail-pop h4{font:600 13px var(--sans);color:var(--text);margin:0 0 6px}+.led-detail-pop .hint{color:var(--muted);margin-top:6px;font-size:12.5px}+.led-detail-pop .winlist{font:12px var(--mono);color:var(--muted);margin-top:6px;white-space:pre-wrap}+/* dash-demo toolbar */+#demoToolbar{position:fixed;left:50%;bottom:56px;transform:translateX(-50%);z-index:99998;display:flex;align-items:center;gap:6px;background:var(--overlay);border:1px solid var(--line);border-radius:999px;padding:6px 12px;box-shadow:0 8px 24px rgba(0,0,0,.45)}+.dt-btn{background:none;border:1px solid transparent;border-radius:8px;color:var(--text);font-size:15px;padding:4px 8px;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}+.dt-btn:hover{background:var(--surface2);border-color:var(--line)}+.dt-btn:active{transform:translateY(1px) scale(.95)}+.dt-btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}+.dt-sep{width:1px;height:18px;background:var(--line)}+.dt-step{font:12px var(--mono);color:var(--muted);padding:0 4px}+#dtMute.muted{opacity:.45}+#dtExit{color:var(--led-red)}+/* status-leds pill row */+.leds{display:flex;flex-wrap:wrap;gap:8px}+.led-pill{display:inline-flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;color:var(--text-body);transition:border-color .15s ease}+.led-pill:hover{border-color:var(--teal)}+.led-pill:active{transform:translateY(1px)}+.led-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}+.led-pill[data-state=ok] .dot{background:var(--led-green)}+.led-pill[data-state=busy] .dot{background:var(--led-amber);animation:ledpulse 1.1s ease-in-out infinite}+.led-pill[data-state=stuck] .dot{background:var(--led-amber)}+.led-pill[data-state=bad] .dot{background:var(--led-red)}+.led-pill .st{color:var(--muted)}+.led-detail-pop{position:fixed;z-index:99998;max-width:360px;background:var(--overlay);border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4)}+.led-detail-pop h4{font:600 13px var(--sans);color:var(--text);margin:0 0 6px}+.led-detail-pop .hint{color:var(--muted);margin-top:6px;font-size:12.5px}+.led-detail-pop .winlist{font:12px var(--mono);color:var(--muted);margin-top:6px;white-space:pre-wrap}+/* dash-demo toolbar */+#demoToolbar{position:fixed;left:50%;bottom:56px;transform:translateX(-50%);z-index:99998;display:flex;align-items:center;gap:6px;background:var(--overlay);border:1px solid var(--line);border-radius:999px;padding:6px 12px;box-shadow:0 8px 24px rgba(0,0,0,.45)}+.dt-btn{background:none;border:1px solid transparent;border-radius:8px;color:var(--text);font-size:15px;padding:4px 8px;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}+.dt-btn:hover{background:var(--surface2);border-color:var(--line)}+.dt-btn:active{transform:translateY(1px) scale(.95)}+.dt-btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}+.dt-sep{width:1px;height:18px;background:var(--line)}+.dt-step{font:12px var(--mono);color:var(--muted);padding:0 4px}+#dtMute.muted{opacity:.45}+#dtExit{color:var(--led-red)}+/* ── newbie-panel (adom-ui-design 1.22.1) ──────────────────────────────────+ ONE full-width band, SIBLING of the app body, two columns: demo left,+ prompts TABLE right. Pills were retired: six phrases of different lengths in+ chips read as a ragged staircase. NO max-width anywhere - a dashboard is an+ app surface, not an article; prose is capped, the layout never is. */+ justify-content:center;background:none;border:1px solid transparent;border-radius:6px;+/* clamp(), NOT minmax() (newbie-panel 1.32.0, found by parts-search): a+ minmax() track resolves to its MAX whenever the neighbouring 1fr can shrink,+ and the content side sets min-width:0 (it must, or a nowrap phrase blows the+ grid out). So minmax(180px,220px) was a HARD 220px at every width - the+ narrow-end flex it looked like it had was unreachable by construction. A+ container percentage genuinely tracks the pane. */+/* 700px (adom-ui-design, 2026-08-25). Was 760, then 620, now 700: his panes+ measure 632, 744 and 851 SIMULTANEOUSLY - there is no single pane width -+ and at 632 the two-column layout truncated the very qualifier the strip+ exists to teach. 700 puts 632 in one column and 744/851 in two.+ which sits BELOW 760, so the two-column panel never rendered in the pane it+ was designed for: the app looked compliant and every screenshot he saw was+ the collapsed fallback. A breakpoint bug is invisible to anyone whose test+ widths straddle it, so 744 is now in my matrix. */+ border:none;border-radius:8px;padding:10px 18px;font:600 14px var(--sans);cursor:pointer;+/* prompts TABLE: one left edge, one row height, so the eye reads phrases not boxes */+/* the row is a grid so the ACTIONS TRACK THE PHRASE, not the window edge: a+ Copy button 2000px from its text on a wide monitor is the bug this fixes */+ background:none;border:1px solid transparent;border-radius:6px;color:var(--muted);cursor:pointer;+@media (pointer:coarse){.ibtn{padding:8px;width:44px;height:44px}}+/* status-leds pill row */+.leds{display:flex;flex-wrap:wrap;gap:8px}+.led-pill{display:inline-flex;align-items:center;gap:7px;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;font-size:12.5px;cursor:pointer;color:var(--text-body);transition:border-color .15s ease}+.led-pill:hover{border-color:var(--teal)}+.led-pill:active{transform:translateY(1px)}+.led-pill .dot{width:8px;height:8px;border-radius:50%;background:var(--muted);flex:none}+.led-pill[data-state=ok] .dot{background:var(--led-green)}+.led-pill[data-state=busy] .dot{background:var(--led-amber);animation:ledpulse 1.1s ease-in-out infinite}+.led-pill[data-state=stuck] .dot{background:var(--led-amber)}+.led-pill[data-state=bad] .dot{background:var(--led-red)}+.led-pill .st{color:var(--muted)}+.led-detail-pop{position:fixed;z-index:99998;max-width:360px;background:var(--overlay);border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4)}+.led-detail-pop h4{font:600 13px var(--sans);color:var(--text);margin:0 0 6px}+.led-detail-pop .hint{color:var(--muted);margin-top:6px;font-size:12.5px}+.led-detail-pop .winlist{font:12px var(--mono);color:var(--muted);margin-top:6px;white-space:pre-wrap}+/* dash-demo toolbar */+#demoToolbar{position:fixed;left:50%;bottom:56px;transform:translateX(-50%);z-index:99998;display:flex;align-items:center;gap:6px;background:var(--overlay);border:1px solid var(--line);border-radius:999px;padding:6px 12px;box-shadow:0 8px 24px rgba(0,0,0,.45)}+.dt-btn{background:none;border:1px solid transparent;border-radius:8px;color:var(--text);font-size:15px;padding:4px 8px;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}+.dt-btn:hover{background:var(--surface2);border-color:var(--line)}+.dt-btn:active{transform:translateY(1px) scale(.95)}+.dt-btn:focus-visible{outline:2px solid var(--teal);outline-offset:2px}+.dt-sep{width:1px;height:18px;background:var(--line)}+.dt-step{font:12px var(--mono);color:var(--muted);padding:0 4px}+#dtMute.muted{opacity:.45}+#dtExit{color:var(--led-red)}+/* "Say it": the first thing on the page (John, 2026-08-23: teach users how to ASK for the app by its noun, first and foremost). */+h2{font-family:'Familjen Grotesk',sans-serif;font-size:17px;color:var(--text);margin:26px 0 12px}+.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px}+.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px}+.led-row{display:flex;align-items:center;gap:10px}+.led{width:12px;height:12px;border-radius:999px;background:var(--text-2);flex:none;transition:background .15s ease}+.led.on{background:var(--green);box-shadow:0 0 8px rgba(63,185,80,.5)}+.led.off{background:var(--red)}+.led.warn{background:var(--yellow)}+.led.dim{background:var(--text-2)}+.led.busy{background:var(--accent);animation:ledpulse 1.1s ease-in-out infinite}+@keyframes ledpulse{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(0,184,177,.55)}+ 50%{opacity:.55;box-shadow:0 0 10px 3px rgba(0,184,177,.25)}}+.card.busy{border-color:rgba(0,184,177,.45)}+.led-label{font-weight:500;color:var(--text)}+.led-detail{font-size:12.5px;color:var(--text);margin-top:6px}+.led-sub{font-size:12.5px;color:var(--text-2);margin-top:6px}+.btn{display:inline-flex;align-items:center;gap:8px;min-height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--border);background:var(--elevated);color:var(--text);font:500 14px 'Satoshi',sans-serif;cursor:pointer;transition:background .15s ease,border-color .15s ease,transform .06s ease}+.btn:hover{background:var(--overlay);border-color:#4b5563}+.btn:active{transform:translateY(1px) scale(.98);background:var(--surface)}+.btn.primary{background:var(--accent);border-color:var(--accent);color:#05221f}+.btn.primary:hover{background:var(--accent-hover)}+.btn.primary:active{background:#009d97;border-color:#009d97;transform:translateY(1px) scale(.98)}+.btn:disabled{opacity:.5;cursor:not-allowed}+.btn:focus-visible,.iconbtn:focus-visible,.tpill:focus-visible,.toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}+.btnrow{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}+.prompt{display:flex;align-items:center;gap:10px;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:10px 12px;margin-bottom:8px;transition:border-color .15s ease}+.prompt:hover{border-color:#4b5563}+input:focus-visible{outline:none;border-color:var(--accent) !important}+.prompt code{font:13px 'JetBrains Mono',monospace;color:var(--text-body);flex:1;white-space:normal}+.copy{flex:none}+.bar{height:8px;background:var(--elevated);border:1px solid var(--border);border-radius:999px;overflow:hidden;margin-top:10px;display:none}+.bar>div{height:100%;width:0;background:var(--accent);transition:width .5s ease}+/* live-frame states (fusion timing contract): a non-measured estimate renders+ DIMMED so a guess never looks as authoritative as a measurement; overdue or+ waiting-on-a-human renders AMBER; percent:null renders an indeterminate+ marquee, never a fake number. */+.sbar.dim>div{opacity:.45}+.sbar.amber>div{background:#d29922}+.sbar.indet>div{width:30%!important;animation:marquee 1.2s linear infinite}+@keyframes marquee{0%{margin-left:-30%}100%{margin-left:100%}}+.barlabel{font-size:12.5px;color:var(--text-2);margin-top:6px}+.scard{display:flex;flex-direction:column;gap:8px}+.scard .btn{width:100%;justify-content:center;text-align:center}+.slog{font:12px 'JetBrains Mono',monospace;color:var(--text-2);min-height:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}+.sbar{height:6px;background:var(--elevated);border:1px solid var(--border);border-radius:999px;overflow:hidden;display:none}+.sbar>div{height:100%;width:0;background:var(--accent);transition:width .4s ease}+.sshot{border:1px solid var(--border);border-radius:8px;overflow:hidden;display:none}+.sshot img{width:100%;display:block}+.scard.err .slog{color:var(--red)}+.shots{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;margin-top:12px}+.shot{background:var(--elevated);border:1px solid var(--border);border-radius:8px;overflow:hidden}+.shot img{width:100%;display:block}+.shot .cap{font-size:11.5px;color:var(--text-2);padding:6px 8px}+#toasts{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;flex-direction:column-reverse;gap:8px;z-index:400}+.toast{background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--teal);+ border-radius:8px;padding:10px 14px;color:var(--text);font-size:14px;box-shadow:0 8px 24px rgba(0,0,0,.4);+ animation:toastin 180ms ease-out}+@keyframes toastin{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}+.toast.out{animation:toastout 140ms ease-in forwards}+@keyframes toastout{to{opacity:0}}+@media (prefers-reduced-motion:reduce){.toast,.toast.out{animation:none}}+.toast.ok{border-left-color:var(--led-green)}.toast.err{border-left-color:var(--led-red)}+dialog{margin:auto;background:var(--surface);color:var(--text-body);border:1px solid var(--border);border-radius:12px;padding:20px;max-width:520px;width:92vw}+/* The backdrop law (settings-layout skill): dim 20-30%, NEVER blur, one value+ for every dialog in the app - settings often change the very thing behind+ the modal and the user must be able to watch it change. */+dialog::backdrop{background:rgba(0,0,0,.25)}++/* ---- Settings modal: the Adom settings surface (settings-layout skill) ---- */+dialog.settings{width:min(660px,92vw);max-width:none;padding:0;overflow:hidden;border-radius:12px;background:var(--surface)}+dialog.settings:focus{outline:none}+.settings-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px 12px 18px;border-bottom:1px solid var(--border);cursor:move;user-select:none}+.settings-head h2{margin:0;font:600 15px/1.3 var(--brand-heading);color:var(--text);display:flex;align-items:center;gap:10px}+.settings-head .card-status{font-family:var(--brand-body)}+.settings-body{max-height:70vh;overflow:auto;padding:18px;scrollbar-gutter:stable}+.settings-card{background:var(--elevated);border:1px solid var(--border);border-left:3px solid color-mix(in srgb,var(--accent) 45%,transparent);border-radius:10px;padding:.9rem 1.1rem 1rem;margin-bottom:1rem}+.card-title{font:600 13px/1.3 var(--brand-heading);color:var(--text);display:flex;align-items:center;gap:10px;margin:0 0 .45rem}+.card-status{font-size:11px;color:var(--text-2);font-weight:400;font-family:var(--brand-body)}+.card-status.ok{color:var(--green)}+.card-desc{font-family:var(--brand-body);font-size:12px;line-height:1.55;color:var(--text-2);max-width:60ch;margin:0 0 .55rem}+.card-desc code{font:11px 'JetBrains Mono',monospace;background:rgba(110,118,129,.2);padding:1px 4px;border-radius:4px}+.divider{height:1px;background:var(--border);margin:.55rem 0}+.wrapper{padding:.5rem 0;display:flex;flex-direction:column;gap:.25rem}+.name{margin:.125rem 0;font-family:var(--brand-heading);font-size:.9375rem;font-weight:600}+.prefix{color:var(--text)}+.title{color:#8fd3d4}+.default-hint{font-family:var(--brand-body);font-size:.75rem;color:var(--text-2)}+.setting-shot{margin:0 0 .75rem}+.shot-pair{display:flex;flex-wrap:wrap;gap:.75rem}+.shot-pair .setting-shot{flex:1 1 272px;max-width:272px}+.setting-shot figcaption b{color:var(--text-body);font-weight:600}+.setting-shot img{display:block;max-width:100%;height:auto;border:1px solid var(--border);border-radius:8px;background:var(--surface)}+.setting-shot figcaption{margin-top:.35rem;font-family:var(--brand-body);font-size:.75rem;color:var(--text-2)}+.choice-set{border:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.75rem}+.choice{flex:1 1 280px;display:flex;flex-direction:column;gap:.35rem;padding:.6rem;cursor:pointer;position:relative;background:var(--surface);border:1px solid var(--border);border-radius:8px;transition:border-color .15s ease,background .15s ease,transform .06s ease}+.choice input{position:absolute;opacity:0;pointer-events:none}+.choice img{display:block;width:100%;max-width:272px;height:auto;border-radius:4px;background:var(--bg);image-rendering:auto}+.choice-name{font-family:var(--brand-heading);font-size:.875rem;font-weight:600;color:var(--text)}+.choice-desc{font-family:var(--brand-body);font-size:.75rem;color:var(--text-2);line-height:1.5}+.choice:hover{border-color:var(--accent);background:var(--elevated)}+.choice:active{transform:translateY(1px) scale(.995)}+.choice[data-selected='true']{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}+.choice:focus-within{outline:2px solid var(--accent);outline-offset:2px}+.setting-row{display:flex;align-items:center;gap:10px}+.set-check-label{font-family:var(--brand-body);font-size:.875rem;color:var(--text-body)}+.toggle{background:none;border:none;padding:2px;cursor:pointer;flex-shrink:0;transition:transform .06s ease}+.toggle-track{display:block;width:32px;height:18px;background:var(--overlay);border:1px solid var(--border);border-radius:9px;position:relative;transition:background .15s ease,border-color .15s ease}+.toggle:hover .toggle-track{border-color:var(--accent)}+.toggle:active{transform:translateY(1px) scale(.97)}+.toggle.active .toggle-track{background:var(--accent);border-color:var(--accent)}+.toggle-thumb{position:absolute;top:2px;left:2px;width:12px;height:12px;background:#fff;border-radius:50%;transition:transform .18s ease,width .12s ease}+.toggle.active .toggle-thumb{transform:translateX(14px)}+.toggle:active .toggle-thumb{width:16px}+.toggle.active:active .toggle-thumb{transform:translateX(10px)}+.toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:10px}+.settings-footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:.5rem}+.footer-note{font-family:var(--brand-body);font-size:.75rem;color:var(--text-2);margin:0;max-width:60ch}+.restore-btn{width:fit-content;display:inline-flex;align-items:center;gap:.4rem;padding:.4rem .85rem;background:transparent;border:1px solid var(--border);border-radius:.375rem;color:var(--text-body);font-family:var(--brand-heading);font-size:.8125rem;font-weight:600;cursor:pointer;transition:background .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}+.restore-btn:hover{background:var(--elevated)}+.restore-btn:active{transform:translateY(1px) scale(.98)}+.restore-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}+.restore-btn.armed{color:var(--red);border-color:rgba(255,107,107,.5);background:rgba(255,107,107,.08)}+@media (max-width:720px){.meta{display:none}.grid{grid-template-columns:1fr 1fr}}+@media (max-width:480px){.grid{grid-template-columns:1fr}.brand span{display:none}.subject{display:none}.vsep{display:none}header{gap:8px}}+#tt{position:fixed;z-index:99999;background:var(--overlay);border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:12.5px;color:var(--text-body);max-width:260px;pointer-events:none;opacity:0;transform:translateY(3px);transition:opacity .18s ease,transform .18s ease}+#tt.show{opacity:1;transform:none}+#appinfo{position:fixed;z-index:99998;background:var(--overlay);border:1px solid var(--border);border-radius:10px;padding:12px 14px;font-size:13px;color:var(--text-body);box-shadow:0 8px 24px rgba(0,0,0,.4);display:none;pointer-events:auto;min-width:220px}+#appinfo.show{display:block}+#appinfo a{color:var(--accent)}+main a{color:var(--accent);text-decoration:none;border-bottom:1px solid rgba(0,184,177,.35)}+main a:hover{border-bottom-color:var(--accent)}+#targets{display:flex;gap:8px;margin:0 0 14px;flex-wrap:wrap}+.tpill{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;border:1px solid var(--border);background:var(--surface);color:var(--text-body);cursor:pointer;font:500 13.5px 'Satoshi',sans-serif;transition:background .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}+.tpill:hover{border-color:#4b5563;background:var(--elevated);color:var(--text)}+.tpill:active{transform:translateY(1px) scale(.97)}+.tpill.active{border-color:var(--accent);color:var(--text);background:var(--elevated)}+.tdot{width:8px;height:8px;border-radius:999px;background:var(--text-2)}+.tdot.on{background:var(--green)}+.note{font-size:12.5px;color:var(--text-2);margin:-6px 0 12px}+.wins{font:12.5px 'JetBrains Mono',monospace;color:var(--text-2)}+</style>+</head>+<body>+<header>+ <div class="brand" id="brand" tabindex="0">+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex:none"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="M7.5 15.5H9.5L16.5 8.5"/><circle cx="7.5" cy="15.5" r="2.5"/><circle cx="16.5" cy="8.5" r="2.5"/></svg>+ Altium Dashboard <span class="sub" style="color:var(--text-2);font-weight:400">· the Altium bridge</span>+ </div>+ <div class="vsep"></div>+ <!-- dash-header: the standard target chip (which box answers), LED inside;+ versions live in the brand popover, NEVER in the bar -->+ <span class="target-chip" id="targetChip" data-tip="The box this bridge answers on. Switch boxes with the pills under Status."><span class="tdot" id="chipDot"></span><span id="chipName">connecting...</span></span>+ <div class="actions">+ <button class="iconbtn" id="btnRefresh" data-tip="Re-read every facet's status from the bridge right now (also auto-refreshes every 15s)" aria-label="Refresh"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"/></svg></button>+ <button class="iconbtn" id="btnHelp" aria-pressed="true" aria-label="Hide the prompts panel" data-tip="Hide the prompts panel"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M11 18h2v-2h-2v2m1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/></svg></button>+ <button class="iconbtn" id="btnSettings" title="Settings" aria-label="Settings"><!-- the shared Adom cog, OUTLINE per the 2026-08-24 fleet ruling (mdi:cog-outline; filled is deprecated) --><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 8a4 4 0 0 1 4 4a4 4 0 0 1-4 4a4 4 0 0 1-4-4a4 4 0 0 1 4-4m0 2a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2m-2 12c-.25 0-.46-.18-.5-.42l-.37-2.65c-.63-.25-1.17-.59-1.69-.99l-2.49 1.01c-.22.08-.49 0-.61-.22l-2-3.46a.493.493 0 0 1 .12-.64l2.11-1.66L4.5 12l.07-1l-2.11-1.63a.493.493 0 0 1-.12-.64l2-3.46c.12-.22.39-.31.61-.22l2.49 1c.52-.39 1.06-.73 1.69-.98l.37-2.65c.04-.24.25-.42.5-.42h4c.25 0 .46.18.5.42l.37 2.65c.63.25 1.17.59 1.69.98l2.49-1c.22-.09.49 0 .61.22l2 3.46c.13.22.07.49-.12.64L19.43 11l.07 1l-.07 1l2.11 1.63c.19.15.25.42.12.64l-2 3.46c-.12.22-.39.31-.61.22l-2.49-1c-.52.39-1.06.73-1.69.98l-.37 2.65c-.04.24-.25.42-.5.42zm1.25-18l-.37 2.61c-1.2.25-2.26.89-3.03 1.78L5.44 7.35l-.75 1.3L6.8 10.2a5.55 5.55 0 0 0 0 3.6l-2.12 1.56l.75 1.3l2.43-1.04c.77.88 1.82 1.52 3.01 1.76l.37 2.62h1.52l.37-2.61c1.19-.25 2.24-.89 3.01-1.77l2.43 1.04l.75-1.3l-2.12-1.55c.4-1.17.4-2.44 0-3.61l2.11-1.55l-.75-1.3l-2.41 1.04a5.42 5.42 0 0 0-3.03-1.77L12.75 4z"/></svg></button>+ + </div>+</header>+<main>+ <!-- The panel is MOUNTED, not built: adom-ui-design 2.0.0 ships the one+ implementation as a module. Seven apps hand-building from a written spec+ produced four paddings, two border colours and three Copy drawings; a+ written spec is not a mechanism. We pass phrases, noun and demo only. -->+ <div id="newbie"></div>+ <script src="newbie-panel.js"></script>++ <!-- status-leds: one compact pill row, prose on tap only -->+ <div class="leds" id="ledRow" style="margin-top:16px"></div>+ <div id="ledDetail" class="led-detail-pop" style="display:none"></div>++ <h2>Boxes & actions</h2>+ <div id="targets"></div>+ <div class="note" id="boxnote" style="display:none">Many boxes, one bridge: pick where Altium should run. Everything the bridge does happens in the background; it tells you before it ever brings a window forward.</div>+ <div class="btnrow">+ <button class="btn primary" id="btnSetup">Setup</button>+ <button class="btn" id="btnLaunch">Launch Altium</button>+ <button class="btn" id="btnClose">Close Altium</button>+ </div>++ <h2>Demos</h2>+ <div class="card">+ <div class="led-label">Guided demo</div>+ <div class="led-sub">Six beats on the examples Altium ships with: symbol, footprint, 3D part, schematic, 2D board, 3D board. Runs entirely in the background through the resident agent, and each finished beat streams back a screenshot.</div>++ <div class="bar" id="demoBar"><div></div></div>+ <div class="barlabel" id="demoLabel"></div>+ <div class="shots" id="demoShots"></div>+ </div>+ <div class="card" style="margin-top:12px">+ <div class="led-label">Tour a wiki part end to end</div>+ <div class="led-sub">Importing a wiki part into Altium (symbol, footprint, STEP body, parameters) is a creative job your AI does with the altium_create_* verbs and proves with frames. Send it the phrase below.</div>+ <div class="btnrow">+ <input id="partPage" value="adom/lan7800" data-tip="Any wiki part page with a symbol, footprint and STEP body" style="flex:1;min-width:180px;background:var(--elevated);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:8px 10px;font:13px 'JetBrains Mono',monospace">+ <button class="btn" id="btnPartTour">Ask the AI to import it</button>+ </div>+ <div class="barlabel" id="partLabel"></div>+ </div>++ <h2>Altium surfaces</h2>+ <div class="note" id="surfaceIntro">Every design surface the bridge can drive, on the examples Altium ships with. Each button opens JUST that surface in the background through the resident DelphiScript agent and shows you the frame the verb returned.</div>+ <div class="grid" id="surfaceGrid"></div>++ <h2 style="display:flex;align-items:center;gap:10px">Verb times on this box+ <button class="btn" id="btnVerbTimes" style="font-size:12.5px" data-tip="Measured run times per verb from this box's append-only run log. p90 is the column for hunting slow paths.">refresh</button></h2>+ <div class="note">Every dispatch is logged with its outcome; these numbers converge just by the bridge being used. p50 15s beside p90 400s is two code paths wearing one name.</div>+ <div class="card" id="verbTimes" style="overflow-x:auto"><div class="note">not loaded - press refresh</div></div>++ <h2>Copy-paste prompts for your AI</h2>+ <div id="prompts"></div>+</main>++<dialog id="dlgSettings" class="settings" tabindex="-1" aria-labelledby="settingsTitle">+ <div class="settings-head" id="settingsHead">+ <h2 id="settingsTitle">Altium Bridge Settings <span class="card-status" id="settingsBox"></span></h2>+ <button class="iconbtn" id="btnCloseSettings" aria-label="Close settings" title="Close"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg></button>+ </div>+ <div class="settings-body" id="settingsBody">++ <section class="settings-card" id="card-activity">+ <h3 class="card-title">Taskbar activity <span class="card-status" id="stActivity"></span></h3>+ <p class="card-desc">While a verb is driving Altium, the bridge marks the Altium taskbar button so you can tell the window is busy and who is busy in it. The progress bar is painted through the Windows taskbar API (<code>ITaskbarList3</code>) and sized from this box's own measured verb timings: it fills over the time that verb usually takes here, creeps past the end when a run is overdue, and shows an indeterminate marquee when nothing has been measured yet. The flash is Windows' own attention highlight. The bridge never triggers it, it only decides whether to cancel it while a bar is showing.</p>+ <p class="card-desc">The difference is what each one asks of you. A flash stays lit until you click the button, so it reads as something you have to deal with. A bar removes itself the moment Adom stops driving, so when the bar and the overlay badge are both gone, the window is yours again. Read-only verbs such as status checks never mark activity in any mode.</p>+ <div class="wrapper">+ <h4 class="name" title="activityIndicator"><span class="prefix">Taskbar: </span><span class="title">Activity indicator</span></h4>+ <fieldset class="choice-set" role="radiogroup" aria-labelledby="activityLabel">+ <legend id="activityLabel" class="set-check-label" style="padding:0;margin-bottom:.35rem">What the Altium taskbar button does while Adom is driving it. Each picture is a 1:1 capture of this machine's taskbar at its native resolution.</legend>+ <label class="choice" data-mode="progress" data-selected="false">+ <input type="radio" name="activityIndicator" value="progress">+ <img src="assets/shots/taskbar-progress.png" width="272" height="88" loading="lazy" alt="The Altium taskbar button with a blue progress bar about two thirds full under its icon, neighbours untouched">+ <span class="choice-name">Progress bar</span>+ <span class="choice-desc">Fills over the verb's usual duration on this box, then clears itself. Cancels the OS flash.</span>+ </label>+ <label class="choice" data-mode="flash" data-selected="false">+ <input type="radio" name="activityIndicator" value="flash">+ <img src="assets/shots/taskbar-flash.png" width="272" height="88" loading="lazy" alt="The same Altium taskbar button tinted salmon with a highlighted underline, no progress bar">+ <span class="choice-name">Attention flash</span>+ <span class="choice-desc">Windows' own highlight, left alone. Says something happened, not how long is left, and stays until you click it.</span>+ </label>+ <label class="choice" data-mode="both" data-selected="false">+ <input type="radio" name="activityIndicator" value="both">+ <img src="assets/shots/taskbar-both.png" width="272" height="88" loading="lazy" alt="The same Altium taskbar button tinted salmon and carrying the blue progress bar at the same time">+ <span class="choice-name">Bar and flash</span>+ <span class="choice-desc">Both at once. The bar still clears itself; the tint stays until you click.</span>+ </label>+ <label class="choice" data-mode="none" data-selected="false">+ <input type="radio" name="activityIndicator" value="none">+ <img src="assets/shots/taskbar-none.png" width="272" height="88" loading="lazy" alt="The same Altium taskbar button with no bar and no highlight, only the running dot">+ <span class="choice-name">Nothing</span>+ <span class="choice-desc">The button never changes. You rely on the overlay badge below, or on nothing.</span>+ </label>+ </fieldset>+ <span class="default-hint">Default: Progress bar</span>+ </div>+ </section>++ <section class="settings-card" id="card-overlay">+ <h3 class="card-title">Overlay badge <span class="card-status" id="stOverlay"></span></h3>+ <p class="card-desc">The Adom mark is stamped in the corner of a Altium window's taskbar button while a verb is running against that window, and comes off by itself five minutes after the last verb, so a badge always means "Adom is driving this right now". It is a per-window overlay through the same taskbar API, so it never changes the button's own icon or how Windows groups it, and it is never stamped by read-only verbs like status checks. A bridge restart clears every badge, because a freshly started bridge is driving nothing.</p>+ <div class="shot-pair">+ <figure class="setting-shot">+ <img src="assets/shots/taskbar-overlay.png" width="272" height="88" loading="lazy" alt="The Altium taskbar button with the small teal Adom mark in its top right corner">+ <figcaption><b>On:</b> the Adom mark sits on the Altium PCB Editor button while a verb drives it.</figcaption>+ </figure>+ <figure class="setting-shot">+ <img src="assets/shots/taskbar-none.png" width="272" height="88" loading="lazy" alt="The same Altium taskbar button with no mark on it, only its own icon">+ <figcaption><b>Off:</b> the same button, same moment, nothing added.</figcaption>+ </figure>+ </div>+ <div class="wrapper">+ <h4 class="name" title="overlayBadges"><span class="prefix">Taskbar: </span><span class="title">Adom overlay badge</span></h4>+ <div class="setting-row">+ <button class="toggle" id="tglOverlay" role="switch" aria-checked="true" aria-label="Show the Adom overlay badge while driving"><span class="toggle-track"><span class="toggle-thumb"></span></span></button>+ <span class="set-check-label">Show the badge while Adom is driving</span>+ </div>+ <span class="default-hint">Default: On</span>+ </div>+ </section>++ <footer class="settings-footer">+ <p class="footer-note">Settings are stored on the selected box in <code>%USERPROFILE%\.adom\altium-bridge-settings.json</code> and apply immediately. Restoring returns every option above to its default.</p>+ <button class="restore-btn" id="btnRestore" type="button"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path id="restoreIcon" fill="currentColor" d="M13 3a9 9 0 0 0-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.954 8.954 0 0 0 13 21a9 9 0 0 0 0-18m-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z"/></svg><span id="restoreLabel">Restore defaults</span></button>+ </footer>+ </div>+</dialog>++<dialog id="dlgSetup">+ <h2 style="margin:0 0 8px">Setup</h2>+ <div id="setupBody" class="led-sub" style="font-size:14px"></div>+ <div class="btnrow" style="justify-content:flex-end">+ <button class="btn primary" id="btnSetupNext">Run the next step</button>+ <button class="btn" id="btnCloseSetup">Close</button>+ </div>+</dialog>++<!-- dash-demo player: a remote control, fixed bottom-center while the demo runs -->+<div id="demoToolbar" style="display:none">+ <button class="dt-btn" id="dtPrev" data-tip="Previous step" aria-label="Previous step">⏮</button>+ <button class="dt-btn" id="dtPlay" data-tip="Pause or resume" aria-label="Pause or resume">⏸</button>+ <button class="dt-btn" id="dtNext" data-tip="Next step" aria-label="Next step">⏭</button>+ <span class="dt-sep"></span>+ <button class="dt-btn" id="dtMute" data-tip="Mute or unmute the narration" aria-label="Mute or unmute">🔊</button>+ <span class="dt-step" id="dtStep">step 1/6</span>+ <button class="dt-btn" id="dtExit" data-tip="Stop the demo, clear the caption, silence narration" aria-label="Exit the demo">✕</button>+</div>+<div id="toasts"></div>+<div id="tt" role="tooltip"></div>+<div id="appinfo"></div>+<script>+const $=s=>document.querySelector(s);+const consoleBuf=[];+["log","warn","error"].forEach(k=>{const o=console[k].bind(console);console[k]=(...a)=>{consoleBuf.push(k+": "+a.join(" "));if(consoleBuf.length>300)consoleBuf.shift();o(...a);};});+window.onerror=(m)=>{consoleBuf.push("onerror: "+m);};+function toast(msg,kind){const t=document.createElement('div');t.className='toast '+(kind||'');+ const ICONS={ok:'M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z',err:'M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8 8 8 0 0 1 8-8 8 8 0 0 1 8 8 8 8 0 0 1-8 8'};+ if(ICONS[kind]){t.innerHTML='<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" style="flex:none;color:var(--'+(kind==='ok'?'green':'red')+')"><path fill="currentColor" d="'+ICONS[kind]+'"/></svg><span></span>';t.lastChild.textContent=msg;t.style.display='flex';t.style.alignItems='center';t.style.gap='8px';}+ else t.textContent=msg;+ $('#toasts').appendChild(t);setTimeout(()=>t.remove(),kind==='err'?9000:4000);}+async function api(path,opts){const r=await fetch(path,opts);return r.json();}+async function act(action,args){return api('api/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action,target:curTarget||undefined,args:args||{}})});}++let lastStatus=null;+let curTarget=localStorage.getItem('kdTarget')||'';+let targetsCache=[];+// The adom.cloud proxy STRIPS query strings on sub-paths, so target rides the PATH.+function statusUrl(force){return 'api/status/'+encodeURIComponent(curTarget||'-')+(force?'/force':'');}+async function loadTargets(){+ try{+ const d=await api('api/targets');+ targetsCache=d.targets||[];+ const el=$('#targets');el.innerHTML='';+ $('#boxnote').style.display=targetsCache.length>1?'':'none';+ const known=targetsCache.map(t=>t.name);+ if(targetsCache.length&&(!curTarget||!known.includes(curTarget))){+ if(curTarget)toast(curTarget+' is no longer connected - switching to '+known[0]);+ curTarget=(targetsCache.find(t=>t.altiumRunning)||targetsCache[0]).name;+ localStorage.setItem('kdTarget',curTarget);+ }+ for(const t of targetsCache){+ const b=document.createElement('button');b.className='tpill'+(t.name===curTarget?' active':'');+ const resp=(t.responding===false)?'not responding':(t.altiumRunning?'Altium is running here':'Altium is closed here');+ b.setAttribute('data-tip',resp+(t.altiumVersion?(' - Altium '+t.altiumVersion):''));+ if(t.responding===false)b.style.opacity='.55';+ b.innerHTML='<span class="tdot'+(t.altiumRunning?' on':'')+'"></span>'+t.name;+ b.onclick=()=>{curTarget=t.name;localStorage.setItem('kdTarget',curTarget);loadTargets();refresh();toast('switched to '+t.name)};+ el.appendChild(b);+ }+ }catch(e){}+}+// status-leds (1.9.1): one compact pill row; prose lives in tap-to-expand.+// A busy pill that stays busy past 15 minutes escalates to "stuck?" - a+// busy-for-a-day is broken, not busy (the fusion add-in lesson).+const BUSY_KEY='kdBusySince';+function busySince(){try{return JSON.parse(localStorage.getItem(BUSY_KEY))||{}}catch(e){return{}}}+function busySave(o){try{localStorage.setItem(BUSY_KEY,JSON.stringify(o))}catch(e){}}+function shortState(l){+ // The SERVER emits `short` (#59). The page NEVER truncates prose - that is+ // what produced the fleet's "in talled and pinned".+ return l.short || l.state || 'unknown';+}+function renderLeds(st){+ const g=$('#ledRow');g.innerHTML='';+ const leds=st.leds||{};+ const ORDER=['relay','box','bridge','altium','license','running','agent','update','suite'];+ const keys=ORDER.filter(k=>leds[k]).concat(Object.keys(leds).filter(k=>!ORDER.includes(k)));+ const bs=busySince();const now=Date.now();let bsChanged=false;+ for(const key of keys){+ const l=leds[key];+ let state=l.state==='ok'?'ok':(l.state==='busy'?'busy':(l.state==='err'?'bad':(l.state==='warn'?'busy':'dim')));+ if(state==='busy'){+ if(!bs[key]){bs[key]=now;bsChanged=true;}+ else if(now-bs[key]>15*60*1000)state='stuck';+ }else if(bs[key]){delete bs[key];bsChanged=true;}+ const el=document.createElement('button');el.className='led-pill';el.dataset.state=state;el.dataset.key=key;+ el.innerHTML=`<span class="dot"></span><b>${l.label||key}</b><span class="st">${state==='stuck'?'stuck?':shortState(l)}</span>`;+ el.onclick=(ev)=>{ev.stopPropagation();showLedDetail(el,key,l,state,st);};+ g.appendChild(el);+ }+ if(bsChanged)busySave(bs);+ const chip=$('#chipName');if(chip){chip.textContent=st.target?('target: '+st.target):'no bridge target';}+ const dot=$('#chipDot');if(dot){const ok=(leds.relay||{}).state==='ok'&&(leds.box||{}).state==='ok';dot.className='tdot'+(ok?' on':'');}+}+// tap-to-expand detail: prose, timestamps, and for a red pill the NEXT STEP+// first (the UI cousin of the CLI Hint: contract). The Plugin pill also+// carries the open-Altium-windows list (its old home was a raw mono dump).+function showLedDetail(anchor,key,l,state,st){+ const d=$('#ledDetail');+ let h='<h4>'+(l.label||key)+' · '+(state==='stuck'?'stuck?':(l.state||''))+'</h4>';+ if(state==='bad'&&l._hint)h+='<div><b>Next step:</b> '+l._hint+'</div>';+ if(state==='stuck')h+='<div><b>Next step:</b> this has been busy over 15 minutes, which usually means wedged. Press Refresh; if it stays stuck, relaunch from the dock (the version-aware serve replaces a stale instance).</div>';+ if(l.detail)h+='<div style="margin-top:6px">'+l.detail+'</div>';+ if(l._hint&&state!=='bad')h+='<div class="hint">'+l._hint+'</div>';+ if(key==='running'||key==='altium'){+ const w=(st&&st.windows)||[];+ h+='<div class="winlist">'+(w.length?('open Altium windows:\n'+w.join('\n')):'no Altium windows open')+'</div>';+ }+ d.innerHTML=h;d.style.display='block';+ const r=anchor.getBoundingClientRect(),M=8;+ let x=Math.max(M,Math.min(r.left,innerWidth-d.offsetWidth-M));+ let y=r.bottom+8;if(y+d.offsetHeight>innerHeight-M)y=r.top-d.offsetHeight-8;+ d.style.left=x+'px';d.style.top=Math.max(M,y)+'px';+}+document.addEventListener('click',()=>{const d=$('#ledDetail');if(d)d.style.display='none';});+const LED_SKELETON=['relay','box','bridge','altium','license','running','agent','update'];+function renderSkeleton(){+ const g=$('#ledRow');if(g.children.length)return; // only before the first real paint+ g.innerHTML=LED_SKELETON.map(k=>`<span class="led-pill" data-state="dim"><span class="dot"></span><b style="text-transform:capitalize">${k}</b><span class="st">checking...</span></span>`).join('');+ const chip=$('#chipName');if(chip)chip.textContent='checking '+(curTarget||'the default box')+'...';+}+let refreshing=false;+async function refresh(force){+ if(refreshing)return;refreshing=true;renderSkeleton();+ try{lastStatus=await api(statusUrl(force));renderLeds(lastStatus);maybeCountLaunch(lastStatus);}+ catch(e){+ const chip=$('#chipName');if(chip)chip.textContent='status unavailable';+ $('#ledRow').innerHTML='<button class="led-pill" data-state="bad"><span class="dot"></span><b>Status</b><span class="st">unavailable</span></button>';+ toast('status failed: '+e+' - pick another box with the pills below, or press Refresh','err');+ }+ finally{refreshing=false;}+}+$('#btnRefresh').onclick=()=>{toast('refreshing status');refresh(true);};+$('#btnLaunch').onclick=async()=>{toast('launching Altium in the background - watch the Running LED');await act('launch');};+$('#btnClose').onclick=async()=>{toast('closing Altium');await act('close');setTimeout(()=>refresh(true),4000);};++// Phrases come from dockbar.json `say` via the server, then the MODULE owns+// everything else: card, grid, table, icons, clipboard, send, toasts, X and+// the header toggle. We only tell it our noun and what the demo does.+function buildSay(){+ // Phrases are injected into the document at serve time (window.__SAY_PHRASES__),+ // so there is no fetch to race at boot. The module may still be a moment+ // behind if its <script> is slow, so poll briefly for it rather than+ // returning and leaving the page with no panel.+ const phrases=(window.__SAY_PHRASES__||[]).slice(0,6);+ if(!phrases.length)return;+ const attempt=()=>{+ if(!window.AdomNewbiePanel)return false;+ try{ doMount(phrases); }catch(e){ console.error('newbie panel mount failed',e); }+ return true;+ };+ if(attempt())return;+ let tries=0;+ const t=setInterval(()=>{ if(attempt()||++tries>20)clearInterval(t); },250);+}+function doMount(phrases){+ AdomNewbiePanel.mount({+ mount:'#newbie', app:'altium', appName:'Altium Dashboard', slug:'altium-bridge',+ toggleButton:'#btnHelp',+ demo:{label:'Play the demo', meta:'6 views, about 2 minutes',+ onPlay:function(){ startDemo(); }},+ phrases:phrases.slice(0,6)+ });+}+// Guided demo with a REAL progress bar + the stepShot stream (one screenshot per finished beat).+async function startDemo(){+ toast('starting the guided demo (background)');+ $('#demoShots').innerHTML='';+ const s=await act('demo',{muted:demoMuted()});const d=s.data||s;+ if(d.success===false){toast('demo failed to start: '+(d.error||'unknown'),'err');return;}+ $('#demoBar').style.display='block';+ $('#demoToolbar').style.display='flex';+ const poll=setInterval(async()=>{+ const p=await act('demo_progress');const q=p.data||p;+ const paused=(q.stepLabel==='Paused');+ $('#dtPlay').dataset.paused=paused?'1':'0';+ $('#dtPlay').innerHTML=paused?'▶':'⏸';+ if(q.beatIndex&&q.beatCount)$('#dtStep').textContent='step '+q.beatIndex+'/'+q.beatCount;+ $('#demoBar>div').style.width=(q.percent||0)+'%';+ $('#demoLabel').textContent=(q.percent||0)+'% '+(q.stepLabel||'');+ if(q.stepShot&&q.stepShot.image){+ const c=document.createElement('div');c.className='shot';+ c.innerHTML='<img src="data:image/png;base64,'+q.stepShot.image+'" alt="'+(q.stepShot.title||'')+'"><div class="cap">'+(q.stepShot.title||q.stepShot.step||'')+'</div>';+ $('#demoShots').appendChild(c);+ }+ if(q.done){clearInterval(poll);$('#demoToolbar').style.display='none';toast(q.error?('demo failed: '+q.error):(q.result&&q.result.exited?'demo stopped':'demo complete'),'' + (q.error?'err':'ok'));refresh(true);}+ },3000);+}+// Demo transport: mirrors the ab toast buttons; the bridge applies commands+// between beats (a beat that is mid-open finishes first).+async function demoCtl(cmd){+ const r=await act('demo_control',{cmd});const d=(r&&r.data)||r||{};+ if(d.error){toast('demo control failed: '+d.error,'err');return;}+ if(cmd==='pause'){$('#dtPlay').dataset.paused='1';$('#dtPlay').innerHTML='▶';toast('pausing after this step','ok');}+ if(cmd==='play'){$('#dtPlay').dataset.paused='0';$('#dtPlay').innerHTML='⏸';toast('resuming','ok');}+ if(cmd==='exit')toast('stopping the demo','ok');+ if(cmd==='next')toast('skipping ahead','ok');+ if(cmd==='prev')toast('replaying the previous step','ok');+}+$('#btnPartTour').onclick=async()=>{+ const page=$('#partPage').value.trim();+ const b=$('#btnPartTour');b.disabled=true;+ toast('touring '+page+' (clone, install, generate project)');+ $('#partLabel').textContent='working...';+ const r=await act('part_tour',{page});const d=r.data||r;+ b.disabled=false;+ if(d.success===false){toast('part tour failed: '+(d.error||'unknown'),'err');$('#partLabel').textContent=d.error||'failed';}+ else{toast('part installed and project generated','ok');$('#partLabel').textContent=(d.steps||[]).join(' · ');refresh(true);}+};++// "Say it": phrases come from dockbar.json `say` via the server, so the dock+// interstitial and this strip teach the same words. Copy on click; Try it+// stages the phrase into the app's AI thread when the Hydrogen control API is+// reachable, and copies with an honest toast when it is not.+// ---- Settings (settings-layout skill: modal, four dismissals, optimistic saves) ----+// The taskbar captures are native DEVICE pixels (2560-wide display at 150%).+// Show them at naturalWidth / devicePixelRatio so they render 1:1 - the same+// physical size as the taskbar they were cut from - instead of being+// upscaled by the browser on a HiDPI screen (caught by John: "grainy").+function sizeShotsToDevicePixels(){+ const dpr=window.devicePixelRatio||1;+ document.querySelectorAll('#dlgSettings img[width]').forEach(img=>{+ const w=parseInt(img.getAttribute('width'),10);if(!w)return;+ img.style.width=(w/dpr)+'px';img.style.height='auto';img.style.maxWidth='100%';+ });+}+sizeShotsToDevicePixels();window.addEventListener('resize',sizeShotsToDevicePixels);+const SETTINGS_DEFAULTS={activityIndicator:'progress',overlayBadges:true};+const MODE_LABEL={progress:'Progress bar',flash:'Attention flash',both:'Bar and flash',none:'Nothing'};+const dlg=$('#dlgSettings');+// Local mirror: the box's settings file is the truth, but the page reflects a+// click before the round trip and survives a stalled one (skill: local-first).+let settingsView=Object.assign({},SETTINGS_DEFAULTS,JSON.parse(localStorage.getItem('kdSettings')||'{}'));+function rememberSettings(){localStorage.setItem('kdSettings',JSON.stringify(settingsView));}+function renderSettings(){+ const mode=MODE_LABEL[settingsView.activityIndicator]?settingsView.activityIndicator:'progress';+ document.querySelectorAll('#dlgSettings .choice').forEach(c=>{+ const sel=c.dataset.mode===mode;c.dataset.selected=String(sel);c.querySelector('input').checked=sel;});+ $('#stActivity').textContent=MODE_LABEL[mode];$('#stActivity').className='card-status'+(mode!=='none'?' ok':'');+ const on=settingsView.overlayBadges!==false;+ const t=$('#tglOverlay');t.classList.toggle('active',on);t.setAttribute('aria-checked',String(on));+ $('#stOverlay').textContent=on?'on':'off';$('#stOverlay').className='card-status'+(on?' ok':'');+ $('#settingsBox').textContent=curTarget?('on '+curTarget):'';+}+// Bounded, optimistic save: the DOM already shows the click; a failed or+// stalled round trip toasts and reverts to what the box last reported.+async function saveSetting(action,args,revert){+ const ctrl=new AbortController();const timer=setTimeout(()=>ctrl.abort(),5000);+ try{+ const r=await fetch('api/action',{method:'POST',headers:{'Content-Type':'application/json'},+ body:JSON.stringify({action,target:curTarget||undefined,args}),signal:ctrl.signal}).then(x=>x.json());+ const d=(r&&r.data)||r||{};+ if(d.error||d.success===false)throw new Error(d.error||'the bridge refused the change');+ if(d.settings){Object.assign(settingsView,d.settings);rememberSettings();renderSettings();}+ return true;+ }catch(e){+ toast('setting not saved: '+(e.name==='AbortError'?'the box did not answer within 5s':e.message),'err');+ if(revert){Object.assign(settingsView,revert);rememberSettings();renderSettings();}+ return false;+ }finally{clearTimeout(timer);}+}+function openSettings(){+ if(lastStatus&&lastStatus.settings&&Object.keys(lastStatus.settings).length){Object.assign(settingsView,lastStatus.settings);rememberSettings();}+ renderSettings();+ dlg.style.margin='';dlg.style.left='';dlg.style.top='';+ dlg.showModal();+ // Open at the TOP: showModal autofocuses the first focusable child, which+ // scrolls to it. Focus the dialog itself and reset scroll, again next frame.+ dlg.focus();const body=$('#settingsBody');body.scrollTop=0;requestAnimationFrame(()=>{body.scrollTop=0;});+}+$('#btnSettings').onclick=openSettings;+$('#btnCloseSettings').onclick=()=>dlg.close();+// Click OUTSIDE closes: the backdrop is part of the dialog element, so gate on+// the pointer being outside the dialog's own box, or padding clicks close it.+dlg.addEventListener('click',e=>{+ if(e.target!==dlg)return;+ const r=dlg.getBoundingClientRect();+ const inside=e.clientX>=r.left&&e.clientX<=r.right&&e.clientY>=r.top&&e.clientY<=r.bottom;+ if(!inside)dlg.close();+});+// Draggable by its header, clamped to the viewport.+(()=>{const head=$('#settingsHead');let drag=null;+ head.addEventListener('pointerdown',e=>{if(e.target.closest('button'))return;+ const r=dlg.getBoundingClientRect();drag={dx:e.clientX-r.left,dy:e.clientY-r.top};head.setPointerCapture(e.pointerId);});+ head.addEventListener('pointermove',e=>{if(!drag)return;+ const r=dlg.getBoundingClientRect();+ const x=Math.max(0,Math.min(e.clientX-drag.dx,innerWidth-r.width));+ const y=Math.max(0,Math.min(e.clientY-drag.dy,innerHeight-r.height));+ dlg.style.margin='0';dlg.style.left=x+'px';dlg.style.top=y+'px';});+ head.addEventListener('pointerup',()=>{drag=null;});head.addEventListener('pointercancel',()=>{drag=null;});+})();+document.querySelectorAll('#dlgSettings .choice input').forEach(inp=>{inp.addEventListener('change',()=>{+ const prev={activityIndicator:settingsView.activityIndicator};+ settingsView.activityIndicator=inp.value;rememberSettings();renderSettings();+ saveSetting('set_activity_indicator',{mode:inp.value},prev).then(ok=>{if(ok)toast('taskbar activity: '+MODE_LABEL[inp.value],'ok');});+});});+$('#tglOverlay').onclick=()=>{+ const prev={overlayBadges:settingsView.overlayBadges!==false};+ const now=!prev.overlayBadges;+ settingsView.overlayBadges=now;rememberSettings();renderSettings();+ saveSetting('set_overlay_badges',{overlayBadges:now},prev).then(ok=>{if(ok)toast('overlay badge '+(now?'on':'off'),'ok');});+};+// Two-click Restore defaults, auto-disarming after 4s. The button IS the confirmation.+(()=>{const b=$('#btnRestore');let armed=false,timer=null;+ const RESTORE='M13 3a9 9 0 0 0-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.954 8.954 0 0 0 13 21a9 9 0 0 0 0-18m-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8z';+ const ALERT='M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8';+ function disarm(){armed=false;b.classList.remove('armed');$('#restoreLabel').textContent='Restore defaults';$('#restoreIcon').setAttribute('d',RESTORE);clearTimeout(timer);}+ b.onclick=()=>{+ if(!armed){armed=true;b.classList.add('armed');$('#restoreLabel').textContent='Click again to confirm';$('#restoreIcon').setAttribute('d',ALERT);timer=setTimeout(disarm,4000);return;}+ disarm();+ const prev=Object.assign({},settingsView);+ Object.assign(settingsView,SETTINGS_DEFAULTS);rememberSettings();renderSettings();+ saveSetting('restore_settings',{},prev).then(ok=>{if(ok)toast('settings restored to defaults','ok');});+ };+})();++// Setup: walk the not-green facets in order, one action per click.+const SETUP_ORDER=[+ {led:'altium',action:'upgrade',label:'Install Altium (silent, per-user, no UAC)'},+ {led:'update',action:'upgrade',label:'Update Altium to the latest release'},+ {led:'plugin',action:'launch',label:'Launch Altium so the reverse-bridge plugin binds'},+];+function setupPending(){if(!lastStatus)return[];return SETUP_ORDER.filter(s=>{const l=lastStatus.leds[s.led];return l&&l.state!=='ok'&&l.state!=='off';});}+function renderSetup(){+ const p=setupPending();+ $('#setupBody').innerHTML=p.length?('Remaining steps:<br>'+p.map((s,i)=>`${i+1}. ${s.label}`).join('<br>')):'Everything is configured. You are ready to go.';+ $('#btnSetupNext').style.display=p.length?'':'none';+}+$('#btnSetup').onclick=async()=>{await refresh(true);renderSetup();$('#dlgSetup').showModal();};+$('#btnCloseSetup').onclick=()=>$('#dlgSetup').close();+$('#btnSetupNext').onclick=async()=>{+ const p=setupPending();if(!p.length)return;+ toast('running: '+p[0].label);+ await act(p[0].action);+ setTimeout(async()=>{await refresh(true);renderSetup();},5000);+};++// Copy-paste AI prompts (teach the user to drive the bridge through their AI).+const PROMPTS=[+ 'run the altium demo and show me each step screenshot as it arrives',+ 'tour the wiki part aravk/in-s42ata in altium: symbol, footprint, 3d chip, project, schematic, 2d board, 3d board',+ 'is altium up to date on all my boxes? update whatever is behind',+ 'show me the symbol and footprint for the part I just installed',+ 'make a single-component project for this part and show me the 3d board',+ 'export gerbers for the open board and tell me the Altium version stamp',+ 'are any altium windows in my foreground? prove the bridge stayed in the background',+];+$('#prompts').innerHTML=PROMPTS.map(p=>`<div class="prompt"><code>${p}</code><button class="btn copy" onclick="navigator.clipboard.writeText('${p.replace(/'/g,"\\'")}');toast('copied','ok')">Copy</button></div>`).join('');++// Altium surfaces: the bridge's surface ladder, driven through /api/action {surface}.+// Each card owns its own log line, progress bar and rendered screenshot; the+// server streams `surface` SSE events as it stages, opens and renders.+const SURF_CARDS={};+async function buildSurfaces(){+ const r=await act('surfaces');const d=r.data||r;+ const g=$('#surfaceGrid');g.innerHTML='';+ const part=d.part||{};+ $('#surfaceIntro').innerHTML="A real wiki part, end to end: <a href='https://wiki.adom.inc/"++ (part.page||'')+"' target='_blank' rel='noopener'>"+(part.name||'the part')+"</a>. "++ "Each button fetches the part's files from its wiki page, installs them into your real Altium, "++ "opens JUST that surface in the background, and renders it from altium_state. This is the whole "++ "circle: a wiki component to the app the EE designs in.";+ const rows=(d.surfaces||[]).concat([{key:'state',label:'Show Altium state',note:'captures every Altium window as a screenshot'}]);+ for(const s of rows){+ const el=document.createElement('div');el.className='card scard';+ el.innerHTML=`<button class="btn" data-tip="${s.note}">${s.label}${s.key!=='state'?' ('+(part.name||'')+')':''}</button>`++ `<div class="sbar"><div></div></div><div class="slog">not run yet</div>`++ `<div class="sshot"><img alt="${s.label}"></div>`;+ el.querySelector('button').onclick=()=>{+ el.classList.remove('err');+ el.querySelector('.slog').textContent='starting...';+ el.querySelector('.sbar').style.display='block';+ el.querySelector('.sbar>div').style.width='5%';+ act('surface',{surface:s.key});+ };+ SURF_CARDS[s.key]=el;g.appendChild(el);+ }+}+function onSurfaceEvent(e){+ const el=SURF_CARDS[e.surface];if(!el)return;+ const bar=el.querySelector('.sbar');+ if(e.live){ // a real measured frame from the bridge (altium_progress)+ if(e.caller&&!String(e.caller).startsWith('altium-dashboard')&&!gradState().aiDriven){gradSave({aiDriven:true});}+ bar.style.display='block';+ bar.classList.toggle('indet', e.percent==null); // unknown -> marquee, never 0%+ bar.classList.toggle('dim', e.confidence!=='measured'); // a guess never looks measured+ bar.classList.toggle('amber', !!(e.overdue||e.blockedBy));+ if(e.percent!=null)bar.querySelector('div').style.width=e.percent+'%';+ let lbl=e.stepLabel||'';+ if(e.blockedBy)lbl='waiting on you: '+e.blockedBy;+ else if(e.etaSec!=null&&!e.overdue)lbl+=' · ~'+Math.round(e.etaSec)+'s left';+ else if(e.overdue)lbl+=' · taking longer than usual';+ if(lbl)el.querySelector('.slog').textContent=lbl;+ return;+ }+ bar.classList.remove('indet','dim','amber');+ if(e.log)el.querySelector('.slog').textContent=e.log;+ if(typeof e.percent==='number'){bar.style.display='block';bar.querySelector('div').style.width=e.percent+'%';}+ if(e.status==='error'){el.classList.add('err');el.querySelector('.sbar').style.display='none';toast(e.surface+': '+e.log,'err');}+ if(e.status==='done'){+ el.classList.remove('err');+ setTimeout(()=>{el.querySelector('.sbar').style.display='none';},800);+ if(e.image){const box=el.querySelector('.sshot');box.style.display='block';+ box.querySelector('img').src='data:image/png;base64,'+e.image;}+ toast(e.log,'ok');+ }+}++// Live channel (SSE): state pushes, AI toasts, CLI click directives.+const CLICK_TARGETS={help:'#btnHelp',setup:'#btnSetup',launch:'#btnLaunch',close:'#btnClose',demo:'#btnDemo','part-tour':'#btnPartTour',settings:'#btnSettings',refresh:'#btnRefresh'};+async function loadVerbTimes(){+ const box=document.getElementById('verbTimes');+ box.innerHTML='<div class="note">loading...</div>';+ try{+ const r=await fetch('api/verb_times/'+encodeURIComponent(curTarget||''));+ const d=await r.json();+ const verbs=d.verbs||{};+ const keys=Object.keys(verbs);+ if(!keys.length){box.innerHTML='<div class="note">'+(d.error||'no runs logged yet on '+(d.target||'this box'))+'</div>';return;}+ let h='<table style="width:100%;border-collapse:collapse;font-size:12.5px">'+ +'<tr style="text-align:left;color:var(--text-2)"><th>verb</th><th>count</th><th>p50</th><th>p90</th><th>max</th><th>failures</th></tr>';+ for(const k of keys){const v=verbs[k];+ const slow=(v.p90||0)>3*(v.p50||1)&&(v.p90||0)>10; // two code paths wearing one name+ h+='<tr'+(v.failures?' style="color:#f85149"':(slow?' style="color:#d29922"':''))+'>'+ +'<td style="padding:3px 10px 3px 0">'+k+'</td><td>'+v.count+'</td>'+ +'<td>'+(v.p50==null?'-':v.p50+'s')+'</td><td>'+(v.p90==null?'-':v.p90+'s')+'</td>'+ +'<td>'+(v.max==null?'-':v.max+'s')+'</td><td>'+v.failures+'</td></tr>';}+ box.innerHTML=h+'</table>';+ }catch(e){box.innerHTML='<div class="note">verb times unavailable: '+e+'</div>';}+}+document.getElementById('btnVerbTimes').onclick=loadVerbTimes;++function connectSSE(){+ const es=new EventSource('events');+ es.addEventListener('hello',()=>{console.log('sse connected');});+ es.addEventListener('state',e=>{try{const st=JSON.parse(e.data);if(!curTarget||st.target===curTarget){lastStatus=st;renderLeds(st);}}catch(_){}});+ es.addEventListener('toast',e=>{try{const t=JSON.parse(e.data);toast(t.message,t.kind==='info'?'':t.kind);}catch(_){}});+ es.addEventListener('surface',e=>{try{onSurfaceEvent(JSON.parse(e.data));}catch(_){}});+ es.addEventListener('click',e=>{try{+ const {target}=JSON.parse(e.data);const sel=CLICK_TARGETS[target];+ if(sel&&$(sel)){$(sel).click();}else{toast('unknown click target: '+target,'err');}+ }catch(_){}});+ es.onerror=()=>{es.close();setTimeout(connectSSE,3000);};+}+connectSSE();++// Tooltips: body-appended, fixed, clamped to the viewport on BOTH axes.+const tt=$('#tt');let ttTimer=null;+document.addEventListener('mouseover',e=>{+ const el=e.target.closest('[data-tip]');+ if(!el){clearTimeout(ttTimer);tt.classList.remove('show');return;}+ clearTimeout(ttTimer);+ ttTimer=setTimeout(()=>{+ tt.textContent=el.getAttribute('data-tip');+ tt.style.left='0px';tt.style.top='0px';tt.classList.add('show');+ const r=el.getBoundingClientRect(),M=8,w=tt.offsetWidth,h=tt.offsetHeight;+ let x=r.left+r.width/2-w/2, y=r.bottom+10;+ if(y+h>innerHeight-M) y=r.top-h-10;+ x=Math.max(M,Math.min(x,innerWidth-w-M));+ y=Math.max(M,Math.min(y,innerHeight-h-M));+ tt.style.left=x+'px';tt.style.top=y+'px';+ },600);+});+document.addEventListener('mouseout',e=>{if(e.target.closest('[data-tip]')){clearTimeout(ttTimer);tt.classList.remove('show');}});++// Brand hover popover: app identity lives here, not in the header row.+const ai=$('#appinfo');const brand=document.querySelector('.brand');+let aiShow=null,aiHide=null;+function fillAppInfo(){+ const v=lastStatus?lastStatus.dashboardVersion:'';const bv=lastStatus?(lastStatus.bridgeVersion||'?'):'?';+ ai.innerHTML='<b style="color:var(--text)">Altium Dashboard</b><br>dashboard v'+(v||'?')+' \u00b7 bridge v'+bv++ '<br>engine: altium-dashboard (Python) + adom-bridge<br>author: adom<br>'++ '<a href="https://wiki.adom.inc/adom/altium-bridge" target="_blank" rel="noopener">wiki.adom.inc/adom/altium-bridge</a>';+}+brand.addEventListener('click',()=>{fillAppInfo();ai.classList.toggle('show');+ const r=brand.getBoundingClientRect(),M=8;+ ai.style.left=Math.max(M,Math.min(r.left,innerWidth-ai.offsetWidth-M))+'px';+ ai.style.top=Math.min(r.bottom+8,innerHeight-ai.offsetHeight-M)+'px';});+brand.addEventListener('mouseenter',()=>{clearTimeout(aiHide);aiShow=setTimeout(()=>{+ fillAppInfo();ai.classList.add('show');+ const r=brand.getBoundingClientRect(),M=8;+ let x=Math.max(M,Math.min(r.left,innerWidth-ai.offsetWidth-M));+ ai.style.left=x+'px';ai.style.top=Math.min(r.bottom+8,innerHeight-ai.offsetHeight-M)+'px';+},600);});+brand.addEventListener('mouseleave',()=>{clearTimeout(aiShow);aiHide=setTimeout(()=>ai.classList.remove('show'),220);});+ai.addEventListener('mouseenter',()=>clearTimeout(aiHide));+ai.addEventListener('mouseleave',()=>{aiHide=setTimeout(()=>ai.classList.remove('show'),220);});++// FIRST PAINT MUST NOT WAIT ON THE NETWORK (ralph iter-2: /api/targets probes+// every box, so one wedged laptop left the whole page on "connecting..." with+// no LEDs and no pills). Skeleton now, then status and targets INDEPENDENTLY.+// ── newbie-graduation (1.9.1): Fresh -> Graduated -> Recallable ────────────+// Scaffolds fold on dismissal, 3 green launches, or the first AI-driven+// action; the say strip folds LAST (5 launches). The header's ? restores+// everything for this session. Storage is per-app by origin; unreadable+// storage renders Fresh - the safe default is teaching.+const GRAD_KEY='adom-dash-graduation';+function gradState(){try{return JSON.parse(localStorage.getItem(GRAD_KEY))||{}}catch(e){return{}}}+function gradSave(patch){try{localStorage.setItem(GRAD_KEY,JSON.stringify({...gradState(),...patch}))}catch(e){}}+function recalled(){try{return sessionStorage.getItem('adom-dash-recall')==='1'}catch(e){return false}}+// The module owns the panel's open/closed memory and wires the header ?+// itself (we pass toggleButton). Nothing to graduate here any more.+// launch counting: one per page load, when the critical row goes green within 60s+let _launchCounted=false;const _loadT=Date.now();+function maybeCountLaunch(st){+ if(_launchCounted||Date.now()-_loadT>60000)return;+ const l=st.leds||{};+ if(['relay','box','bridge','altium'].every(k=>(l[k]||{}).state==='ok')){+ _launchCounted=true;gradSave({launches:(gradState().launches||0)+1});+ }+}++// ── dash-demo player: toolbar wiring ───────────────────────────────────────+const MUTE_KEY='kdDemoMuted';+function demoMuted(){try{return localStorage.getItem(MUTE_KEY)==='1'}catch(e){return false}}+function setMuteUI(){$('#dtMute').classList.toggle('muted',demoMuted());}+$('#dtMute').onclick=async()=>{+ const m=!demoMuted();try{localStorage.setItem(MUTE_KEY,m?'1':'0')}catch(e){}+ setMuteUI();await act('demo_control',{cmd:m?'mute':'unmute'});+ toast(m?'narration muted':'narration on','ok');+};+$('#dtPlay').onclick=()=>demoCtl($('#dtPlay').dataset.paused==='1'?'play':'pause');+$('#dtPrev').onclick=()=>demoCtl('prev');+$('#dtNext').onclick=()=>demoCtl('next');+$('#dtExit').onclick=()=>demoCtl('exit');+setMuteUI();++// Measure, never assume: report the real viewport of whatever surface we are+// rendered in (ah webview pane, browser, iframe) back to our own server, so+// `GET /api/viewport` can prove which side of the breakpoint we landed on.+(function reportViewport(){+ const send=()=>{try{fetch('api/viewport',{method:'POST',headers:{'Content-Type':'application/json'},+ body:JSON.stringify({innerWidth:window.innerWidth,innerHeight:window.innerHeight,scrollWidth:document.documentElement.scrollWidth,overflowX:document.documentElement.scrollWidth>window.innerWidth,offenders:(()=>{const vw=window.innerWidth,out=[];document.querySelectorAll('body *').forEach(n=>{const r=n.getBoundingClientRect();if(r.right>vw+1||r.width>vw+1){out.push((n.id?'#'+n.id:(n.className&&n.className.toString().split(' ')[0]?'.'+n.className.toString().split(' ')[0]:n.tagName))+':w'+Math.round(r.width)+',r'+Math.round(r.right));}});return out.slice(0,8)})(),+ dpr:window.devicePixelRatio,twoCol:window.innerWidth>700,+ panelCols:(()=>{const g=document.querySelector('.anp-cols')||document.querySelector('.anp');return g?getComputedStyle(g).gridTemplateColumns:null})(),+ panelOpen:!!document.querySelector('.anp'),+ ariaPressed:document.getElementById('btnHelp')?document.getElementById('btnHelp').getAttribute('aria-pressed'):null,+ helpLabel:document.getElementById('btnHelp')?document.getElementById('btnHelp').getAttribute('aria-label'):null,+ headerH:document.querySelector('header')?Math.round(document.querySelector('header').getBoundingClientRect().height):null,+ headerPadX:document.querySelector('header')?getComputedStyle(document.querySelector('header')).paddingLeft:null,+ truncatedPhrases:[...document.querySelectorAll('.anp-phrase,.p-phrase')].filter(n=>n.scrollWidth>n.clientWidth).length,+ totalPhrases:document.querySelectorAll('.anp-phrase,.p-phrase').length})}).catch(()=>{});}catch(e){}};+ send();window.addEventListener('resize',()=>{clearTimeout(window.__vpT);window.__vpT=setTimeout(send,400);});+})();+renderSkeleton();+buildSay();+buildSurfaces();+refresh();+loadTargets();+setInterval(()=>refresh(),15000);setInterval(loadTargets,60000);+</script>+</body>+</html>
dashboard/ui/newbie-panel.jsadded+598@@ -0,0 +1,598 @@+/* Adom newbie panel - the ONE implementation every dock dashboard mounts.+ * Published in adom-ui-design. Do not fork it: fix it here and every app gets the fix.+ * Self-contained: no network, no deps, fonts embedded. Serve it next to your dashboard+ * and <script src="newbie-panel.js"></script>, or inline its text into your page.+ */+(function (root) {+ 'use strict';+ var CSS = `+@font-face{font-family:'Familjen Grotesk';font-style:normal;font-weight:400 700;font-display:swap;+ src:url(data:font/woff2;base64,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) format('woff2-variations');}+@font-face{font-family:'Satoshi';font-style:normal;font-weight:300 900;font-display:swap;+ src:url(data:font/woff2;base64,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) format('woff2-variations');}++.anp-wrap{container-type:inline-size; container-name:anp; width:100%;}+.anp{+ --anp-surface:#161b22; --anp-line:#30363d; --anp-teal:#00b8b1;+ --anp-text:#e6edf3; --anp-muted:#8b949e; --anp-row:#262d36;+ --anp-display:'Familjen Grotesk',ui-sans-serif,system-ui,sans-serif;+ --anp-body:'Satoshi',ui-sans-serif,system-ui,sans-serif;+ box-sizing:border-box; width:100%; margin:16px 0 20px; padding:18px 20px 20px;+ background:var(--anp-surface); border:1px solid var(--anp-line);+ border-left:3px solid var(--anp-teal); border-radius:12px;+ font-family:var(--anp-body); color:var(--anp-text);+ display:grid; grid-template-areas:"head head" "left right";+ grid-template-columns:220px minmax(0,1fr); column-gap:24px; row-gap:14px;+}+.anp *,.anp *::before,.anp *::after{box-sizing:border-box;}+.anp[hidden]{display:none;}++.anp-head{grid-area:head; position:relative; padding-right:40px;}+.anp-head h2{margin:0; font:600 15px/1.3 var(--anp-display); color:var(--anp-text); letter-spacing:-.01em;}+.anp-head p{margin:6px 0 0; font:400 13.5px/1.5 var(--anp-body); color:var(--anp-muted); max-width:78ch;}+.anp-head p b{color:var(--anp-teal); font-weight:500;}++.anp-left{grid-area:left; display:flex; flex-direction:column; align-items:flex-start; gap:8px;}+.anp-right{grid-area:right; min-width:0;}++.anp-demo{+ display:inline-flex; align-items:center; gap:9px; white-space:nowrap;+ font:600 14px/1 var(--anp-display); color:#05221f;+ background:var(--anp-teal); border:0; border-radius:8px; padding:10px 18px;+ cursor:pointer; transition:filter .15s ease;+}+.anp-demo:hover{filter:brightness(1.08);}+.anp-demo:active{filter:brightness(.94);}+.anp-demo:focus-visible{outline:2px solid var(--anp-teal); outline-offset:3px;}+.anp-demo svg{width:14px;height:14px;flex:0 0 auto;}+.anp-meta{font:400 12px/1.4 var(--anp-body); color:var(--anp-muted);}++.anp-tbl{width:100%; max-width:720px; border-collapse:collapse; table-layout:fixed;}+.anp-tbl td{padding:0; border:0; border-bottom:1px solid var(--anp-row); height:40px; vertical-align:middle;}+.anp-tbl tr:last-child td{border-bottom:0;}+.anp-tbl tr{cursor:pointer;}+.anp-tbl tr:hover td{background:rgba(255,255,255,.03);}+.anp-phrase{width:auto; min-width:0;}+.anp-phrase span{display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;+ font:400 14px/1.4 var(--anp-body); color:var(--anp-text);}+.anp-phrase b{color:var(--anp-teal); font-weight:400;}+.anp-acts{width:72px; white-space:nowrap; text-align:right;}+.anp-acts .anp-ib + .anp-ib{margin-left:6px;}++.anp-ib{display:inline-flex; align-items:center; justify-content:center; width:28px; height:28px;+ padding:0; background:none; border:0; border-radius:6px; color:var(--anp-muted); cursor:pointer;+ transition:color .15s ease, background .15s ease;}+.anp-ib:hover{color:var(--anp-teal); background:rgba(255,255,255,.05);}+.anp-ib:focus-visible{outline:2px solid var(--anp-teal); outline-offset:1px;}+.anp-ib svg{width:16px;height:16px;display:block;}+.anp-ib[disabled]{opacity:.45; cursor:default;}++.anp-x{position:absolute; top:-2px; right:0; width:28px; height:28px; padding:0; background:none;+ border:0; border-radius:6px; color:var(--anp-muted); cursor:pointer;}+.anp-x:hover{color:var(--anp-text); background:rgba(255,255,255,.06);}+.anp-x:focus-visible{outline:2px solid var(--anp-teal); outline-offset:1px;}+.anp-x svg{width:14px;height:14px;display:block;margin:auto;}++@container anp (max-width:699px){+ .anp{grid-template-areas:"head" "left" "right"; grid-template-columns:minmax(0,1fr); row-gap:12px;}+ .anp-left{flex-direction:row; align-items:center; gap:12px;}+}++.anp-toasts{position:fixed; left:50%; bottom:22px; transform:translateX(-50%); z-index:99999;+ display:flex; flex-direction:column-reverse; gap:8px; pointer-events:none;}+.anp-toast{pointer-events:auto; display:flex; align-items:center; gap:10px; max-width:min(92vw,520px);+ padding:10px 14px; border-radius:8px; border:1px solid var(--anp-line,#30363d);+ background:#1c2230; color:#e6edf3; font:400 13.5px/1.4 'Satoshi',ui-sans-serif,system-ui,sans-serif;+ box-shadow:0 10px 30px rgba(0,0,0,.45); animation:anp-in .18s ease;}+.anp-toast.ok{border-left:3px solid #3fb950;}+.anp-toast.err{border-left:3px solid #f85149;}+.anp-toast.info{border-left:3px solid #00b8b1;}+@keyframes anp-in{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}+@media (prefers-reduced-motion:reduce){.anp-toast{animation:none;}}++.anp-tip{position:fixed; z-index:99999; pointer-events:none; padding:6px 9px; border-radius:6px;+ background:#0d1117; border:1px solid #30363d; color:#e6edf3; text-transform:none;+ font:400 12px/1.3 'Satoshi',ui-sans-serif,system-ui,sans-serif; box-shadow:0 6px 20px rgba(0,0,0,.5);+ opacity:0; transition:opacity .18s ease;}+.anp-tip.on{opacity:1;}+`;+ var ICON = {+ copy: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"/></svg>',+ send: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.5 2.5 10.5 13.5"/><path d="M21.5 2.5 14.8 21.5l-4.3-8-8-4.3z"/></svg>',+ play: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 4l14 8-14 8z"/></svg>',+ x: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M5 5l14 14M19 5 5 19"/></svg>',+ help: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M9.2 9.3a2.9 2.9 0 0 1 5.6 1c0 1.9-2.8 2.4-2.8 4"/><path d="M12 17.6h.01"/></svg>'+ };+ /* The two-column layout needs, inside the panel's padding:+ * phrase cell 336 + action column 72 + 8 gutter = 416 for the right column+ * left column 220 + column gap 24 + 416 = 660 content+ * + 20 left padding + 20 right padding = 700 PANEL border-box+ * That is where 700 comes from. It is a PANEL width, not a viewport width, so it is the+ * same number in every app regardless of that app's page chrome. */+ var TWO_COL_MIN = 700, PHRASE_BUDGET = 336;+ var MIN_API = [1, 0, 142];++ function css() {+ if (document.getElementById('anp-css')) return;+ var s = document.createElement('style');+ s.id = 'anp-css'; s.textContent = CSS;+ document.head.appendChild(s);+ }+ function el(tag, cls, html) {+ var n = document.createElement(tag);+ if (cls) n.className = cls;+ if (html != null) n.innerHTML = html;+ return n;+ }+ function esc(s) {+ return String(s).replace(/[&<>"]/g, function (c) {+ return { '&': '&', '<': '<', '>': '>', '"': '"' }[c];+ });+ }+ /* Highlight the trigger noun. Built with RegExp(), never a literal, because a dashboard+ * assembled as a JS template literal turns \b into a BACKSPACE and the boundaries vanish+ * silently: the regex still matches, just too greedily. (nb, 2026-08-25) */+ function mark(phrase, noun) {+ var safe = esc(phrase);+ if (!noun) return safe;+ var re = new RegExp('\\b(' + esc(noun).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')\\b', 'gi');+ return safe.replace(re, '<b>$1</b>');+ }++ /* ---- copy, with a fallback that does not need the async clipboard ------+ * navigator.clipboard.writeText needs a secure context AND, in several embedded webviews,+ * transient user activation plus a clipboard-write permission that is simply absent. In+ * ah's webview it rejects with NotAllowedError, so the ONLY path the panel had would fail+ * and the user got "Could not reach the clipboard" on a button that looks like it works.+ * The execCommand path is deprecated but synchronous, needs no permission, and works in+ * exactly the contexts the async API refuses. Try the modern one, fall back, and only then+ * report failure. */+ function copyText(text) {+ return new Promise(function (resolve, reject) {+ function legacy() {+ try {+ var ta = document.createElement('textarea');+ ta.value = text;+ ta.setAttribute('readonly', '');+ ta.style.cssText = 'position:fixed;top:0;left:-9999px;opacity:0';+ document.body.appendChild(ta);+ ta.select(); ta.setSelectionRange(0, ta.value.length);+ var ok = document.execCommand && document.execCommand('copy');+ ta.remove();+ ok ? resolve('legacy') : reject(new Error('execCommand refused'));+ } catch (e) { reject(e); }+ }+ if (navigator.clipboard && navigator.clipboard.writeText) {+ navigator.clipboard.writeText(text).then(function () { resolve('async'); }, legacy);+ } else legacy();+ });+ }++ /* ---- toasts ---------------------------------------------------------- */+ var toastHost = null;+ function toast(msg, kind, ms) {+ if (!toastHost) { toastHost = el('div', 'anp-toasts'); document.body.appendChild(toastHost); }+ var t = el('div', 'anp-toast ' + (kind || 'info'), esc(msg));+ toastHost.appendChild(t);+ setTimeout(function () { t.remove(); }, ms || (kind === 'err' ? 7000 : 4000));+ return t;+ }++ /* ---- one tooltip, viewport-clamped, never a title attribute ----------- */+ var tip = null, tipT = null;+ function tipFor(node, text) {+ node.setAttribute('aria-label', text);+ node.removeAttribute('title'); // two tooltips on one control is a bug+ node.__anpSetTip = function (t) { text = t; if (tip && tip.classList.contains('on')) tip.textContent = t; };+ node.addEventListener('mouseenter', function (e) {+ clearTimeout(tipT);+ tipT = setTimeout(function () {+ if (!tip) { tip = el('div', 'anp-tip'); document.body.appendChild(tip); }+ tip.textContent = text; tip.classList.add('on');+ var r = node.getBoundingClientRect(), w = tip.offsetWidth, h = tip.offsetHeight, M = 8;+ var left = r.left + r.width / 2 - w / 2, top = r.top - h - 8;+ if (top < M) top = r.bottom + 8;+ left = Math.max(M, Math.min(left, document.documentElement.clientWidth - w - M));+ top = Math.max(M, Math.min(top, document.documentElement.clientHeight - h - M));+ tip.style.left = left + 'px'; tip.style.top = top + 'px';+ }, 600);+ });+ ['mouseleave', 'blur', 'click'].forEach(function (ev) {+ node.addEventListener(ev, function () {+ clearTimeout(tipT); if (tip) tip.classList.remove('on');+ });+ });+ }++ /* ---- send to a new AI thread ----------------------------------------- */+ function verNum(s) {+ var p = String(s || '').split('.').map(function (x) { return parseInt(x, 10) || 0; });+ return [p[0] || 0, p[1] || 0, p[2] || 0];+ }+ function verGte(a, b) {+ for (var i = 0; i < 3; i++) { if (a[i] > b[i]) return true; if (a[i] < b[i]) return false; }+ return true;+ }+ function gate(base) {+ /* NUMERIC compare, split on dots. A string compare passes "1.0.99", which is older and+ * still carries the crash. Fail OPEN if the probe itself is unreachable. */+ return fetch(base + '/updater/status', { cache: 'no-store' })+ .then(function (r) { return r.json(); })+ .then(function (j) {+ var v = (j && j.updater && j.updater.current_version) || '0.0.0';+ return { ok: verGte(verNum(v), MIN_API), version: v };+ })+ .catch(function () { return { ok: true, version: null, open: true }; });+ }+ function send(base, phrase, appName, slug) {+ return gate(base).then(function (g) {+ if (!g.ok) throw new Error('Adom needs updating to send prompts (found ' + g.version + ', needs 1.0.142). Run: adom update');+ var ctl = new AbortController(), timer = setTimeout(function () { ctl.abort(); }, 120000);+ /* ONE call. POST /ai-threads/new NEVER returns an id, so a two-step create-then-run+ * flow fails 100% of the time and looks like a platform fault. */+ return fetch(base + '/ai-threads/run-prompt', {+ method: 'POST', signal: ctl.signal,+ headers: { 'content-type': 'application/json' },+ body: JSON.stringify({+ "new": true, prompt: phrase, submit: true,+ name: appName + ' - say it', tags: ['dock:' + slug]+ })+ }).then(function (r) { return r.json().then(function (j) { return { status: r.status, j: j }; }); })+ .then(function (res) {+ clearTimeout(timer);+ var j = res.j || {};+ /* THREE states, never two. delivered_to is an ARRAY; a thread that opened but did+ * not confirm delivery is NOT a failure, and retrying it double-sends. */+ var dt = j.delivered_to, arr = Array.isArray(dt) ? dt : (dt ? [dt] : []);+ if (arr.length || (j.inject && j.inject.delivered_in_conversation)) return { state: 'delivered' };+ if (res.status >= 200 && res.status < 300) return { state: 'opened', hint: j._hints && j._hints.note };+ throw new Error((j._hints && j._hints.note) || j.error || ('AI threads returned ' + res.status));+ })+ .catch(function (e) {+ clearTimeout(timer);+ if (e.name === 'AbortError') return { state: 'opened', hint: 'no answer within 120s' };+ throw e;+ });+ });+ }++ /* ---- mount ------------------------------------------------------------ */+ function mount(cfg) {+ css();+ var host = typeof cfg.mount === 'string' ? document.querySelector(cfg.mount) : cfg.mount;+ if (!host) throw new Error('newbie panel: mount target not found: ' + cfg.mount);+ var noun = cfg.app || '';+ var slug = cfg.slug || noun.replace(/\s+/g, '-');+ var appName = cfg.appName || noun;+ var base = cfg.apiBase == null ? '' : cfg.apiBase;+ var key = 'anp:' + slug + ':open';++ var wrap = el('div', 'anp-wrap');+ var panel = el('section', 'anp');+ panel.setAttribute('aria-label', 'Say it: sample prompts for ' + appName);++ var head = el('div', 'anp-head');+ head.appendChild(el('h2', null, 'Say it'));+ head.appendChild(el('p', null,+ "You never need this page's buttons: just tell your AI what you want, using the words <b>" ++ esc(noun) + "</b>. Copy a phrase, or send it straight to a new AI thread."));+ var x = el('button', 'anp-x', ICON.x);+ x.type = 'button'; tipFor(x, 'Close this panel');+ head.appendChild(x);+ panel.appendChild(head);++ var left = el('div', 'anp-left');+ var demo = el('button', 'anp-demo', ICON.play + '<span>' + esc((cfg.demo && cfg.demo.label) || 'Play the demo') + '</span>');+ demo.type = 'button';+ demo.addEventListener('click', function () {+ if (cfg.demo && typeof cfg.demo.onPlay === 'function') cfg.demo.onPlay();+ else toast('This dashboard has no demo wired up yet', 'err');+ });+ left.appendChild(demo);+ if (cfg.demo && cfg.demo.meta) left.appendChild(el('span', 'anp-meta', esc(cfg.demo.meta)));+ panel.appendChild(left);++ var right = el('div', 'anp-right');+ var tbl = el('table', 'anp-tbl');+ var tb = el('tbody');+ (cfg.phrases || []).slice(0, 6).forEach(function (phrase) {+ var tr = el('tr');+ var td = el('td', 'anp-phrase', '<span>“' + mark(phrase, noun) + '”</span>');+ var ta = el('td', 'anp-acts');++ var cp = el('button', 'anp-ib', ICON.copy); cp.type = 'button';+ tipFor(cp, 'Copy this phrase');+ cp.addEventListener('click', function (e) {+ e.stopPropagation();+ copyText(phrase)+ .then(function () { toast('Copied. Paste it to your AI.', 'ok'); })+ .catch(function () { toast('Could not reach the clipboard. Select the phrase and copy it.', 'err'); });+ });++ var sd = el('button', 'anp-ib', ICON.send); sd.type = 'button';+ tipFor(sd, 'Send to a new AI thread');+ sd.addEventListener('click', function (e) {+ e.stopPropagation();+ if (sd.disabled) return;+ sd.disabled = true;+ var pending = toast('Opening a new AI thread…', 'info', 120000);+ send(base, phrase, appName, slug).then(function (r) {+ pending.remove();+ if (r.state === 'delivered') toast('Sent. Your AI thread has the prompt.', 'ok');+ else toast('Thread opened, delivery not confirmed. Check your AI threads before sending again.', 'info', 8000);+ }).catch(function (err) {+ pending.remove();+ toast(err.message || 'Could not send the prompt.', 'err');+ }).then(function () { sd.disabled = false; });+ });++ ta.appendChild(cp); ta.appendChild(sd);+ tr.appendChild(td); tr.appendChild(ta);+ tr.addEventListener('click', function () { cp.click(); });+ tb.appendChild(tr);+ });+ tbl.appendChild(tb); right.appendChild(tbl); panel.appendChild(right);+ wrap.appendChild(panel); host.appendChild(wrap);++ /* the header ? is a real TOGGLE, both ways, with aria-pressed and a label that swaps */+ var toggleBtn = typeof cfg.toggleButton === 'string'+ ? document.querySelector(cfg.toggleButton) : cfg.toggleButton;+ /* setOpen(open, remember, source)+ * source is 'user' when a person clicked the X or the header toggle, and 'api' when the+ * app called open()/close() itself. It is passed to onToggle, because an app with a+ * graduation policy calls open()/close() from inside its own onToggle handler: without a+ * source the callback cannot tell its own action from a person's, so graduating the panel+ * open pins it open forever and graduating it closed marks it user-dismissed. It looks+ * like "the panel sometimes ignores graduation". (lbr, 2026-08-25) */+ function setOpen(open, remember, source) {+ panel.hidden = !open;+ wrap.style.display = open ? '' : 'none'; /* no dead margin when collapsed */+ if (toggleBtn) {+ var label = open ? 'Hide the sample prompts' : 'Show the sample prompts';+ toggleBtn.setAttribute('aria-pressed', open ? 'true' : 'false');+ toggleBtn.setAttribute('aria-label', label);+ /* the TOOLTIP has to swap with the state too, or a person with the panel already in+ * front of them hovers the control and is told it will show them the panel */+ if (toggleBtn.__anpSetTip) toggleBtn.__anpSetTip(label);+ }+ if (remember !== false) { try { localStorage.setItem(key, open ? '1' : '0'); } catch (e) {} }+ if (typeof cfg.onToggle === 'function') cfg.onToggle(open, source || 'api');+ }+ x.addEventListener('click', function () { setOpen(false, true, 'user'); });+ if (toggleBtn) {+ if (!toggleBtn.innerHTML.trim()) toggleBtn.innerHTML = ICON.help;+ tipFor(toggleBtn, 'Show the sample prompts');+ toggleBtn.addEventListener('click', function () { setOpen(panel.hidden, true, 'user'); });+ }+ var saved = null;+ try { saved = localStorage.getItem(key); } catch (e) {}+ setOpen(saved === null ? true : saved === '1', false);++ /* authoring guard: a phrase that truncates at rest is an authoring bug, and it eats the+ * trigger noun because the noun is usually the last word. Warn loudly at build time. */+ var probe = el('span'); probe.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap';+ document.body.appendChild(probe);+ var over = [];+ (cfg.phrases || []).forEach(function (p) {+ probe.style.font = '400 14px/1.4 Satoshi,ui-sans-serif,system-ui,sans-serif';+ probe.textContent = '“' + p + '”';+ var need = Math.ceil(probe.getBoundingClientRect().width);+ if (need > PHRASE_BUDGET) over.push({ phrase: p, needs: need, budget: PHRASE_BUDGET });+ });+ probe.remove();+ if (over.length) {+ console.warn('[newbie-panel] ' + over.length + ' phrase(s) will truncate at the resting width. ' ++ 'Shorten them, and lead with the trigger noun so a clip costs a detail not the routing words.', over);+ }++ return {+ panel: panel,+ open: function (o) { setOpen(true, !(o && o.remember === false), 'api'); },+ close: function (o) { setOpen(false, !(o && o.remember === false), 'api'); },+ toggle: function () { setOpen(panel.hidden, true, 'api'); },+ isOpen: function () { return !panel.hidden; },+ overBudget: over, toast: toast, selfCheck: function () { return selfCheck(panel); }+ };+ }++ /* ---- selfCheck: prove conformance from one call ---------------------- */+ var CANON = {+ 'card background': ['backgroundColor', 'rgb(22, 27, 34)'],+ 'card border': ['borderTopWidth', 'snap:1'],+ 'card border colour': ['borderTopColor', 'rgb(48, 54, 61)'],+ 'card accent edge': ['borderLeftWidth', 'snap:3'],+ 'card radius': ['borderRadius', '12px'],+ 'card padding': ['padding', '18px 20px 20px'],+ 'column gap': ['columnGap', '24px']+ };+ /* selfCheck(panel) - pass the panel explicitly when the page mounts more than one, or+ * mounts into a shadow root. The no-arg form takes the FIRST .anp in the light DOM, which+ * is right for the single-panel case and silently wrong for the others. Returns+ * {ok:false, error} rather than throwing when there is no panel. (chip-fetcher, 2026-08-25) */+ function selfCheck(panel) {+ panel = panel || document.querySelector('.anp');+ if (!panel) return { ok: false, error: 'no .anp panel on the page' };+ /* REFUSE TO REPORT FROM A COLLAPSED LAYOUT. An unforegrounded tab returns zero-height+ * rects while computed styles still resolve normally, so selfCheck produced EIGHT GREEN+ * ROWS beside two red ones: "panel gutter: 0px left, 744px right" and "columns: want 1,+ * got 3". That mixed shape is far more dangerous than an all-zero one, because it reads+ * as a specific layout regression rather than an invalid run, and project-manager nearly+ * filed it as a bug. A refusal cannot be misread. (project-manager, 2026-08-25.) */+ var w0 = Math.round(panel.getBoundingClientRect().width);+ /* rows: [] is deliberate. The refusal previously omitted the key entirely, and EVERY+ * caller does r.rows.map(...) because every healthy result has rows, so a correct refusal+ * arrived at the caller as "Cannot read properties of undefined (reading 'map')" and read+ * like harness noise. kicad hit exactly that and logged it as its own crash. A guard whose+ * failure mode is a TypeError in someone else's stack is a guard that will be misread. */+ if (w0 === 0) return { ok: false, invalid: true, rows: [],+ error: 'panel measures 0px wide: this tab is not foregrounded (or the panel is hidden), ' ++ 'so every layout row would ' ++ 'be computed from a collapsed pane while the style rows pass normally. Activate ' ++ 'the tab by name (or open the panel) and re-run. No rows reported, because a ' ++ 'partial result from an unrendered pane looks like a real bug.' };+ var cs = getComputedStyle(panel), rows = [], ok = true;+ /* Chrome reports the USED border-width, snapped to whole device pixels, so a declared+ * 1px reads as 0.666667px at dpr 1.5. Compare against the snapped value, not the+ * declared one, or every panel fails a check it actually passes. */+ function snap(declared) {+ var dpr = window.devicePixelRatio || 1;+ return (Math.max(1, Math.floor(declared * dpr)) / dpr) + 'px';+ }+ Object.keys(CANON).forEach(function (k) {+ var want = CANON[k][1];+ var got = cs[CANON[k][0]], declared = CANON[k][1];+ /* Border widths need BOTH candidates accepted. Chrome reports the USED width, snapped+ * to whole device pixels, on some surfaces (ah's webview and pup on a dpr-1.5 laptop+ * both return 0.666667px for a declared 1px) and reports the DECLARED width on others+ * (an emulated dpr returns 1px at every ratio). Accepting only one of the two fails a+ * correct panel in the other environment, and at dpr 1.5 specifically, which is the+ * machine this fleet is reviewed on. Found by nb, 2026-08-25. */+ var pass;+ if (declared.indexOf('snap:') === 0) {+ var d = parseFloat(declared.slice(5)), g = parseFloat(got);+ pass = Math.abs(g - d) < 0.01 || Math.abs(g - parseFloat(snap(d))) < 0.01;+ want = d + 'px or ' + snap(d);+ } else {+ pass = (parseFloat(want) && parseFloat(got))+ ? Math.abs(parseFloat(got) - parseFloat(want)) < 0.01+ : got === want;+ }+ if (!pass) ok = false;+ rows.push({ what: k, want: want, got: got, pass: pass });+ });+ var copies = panel.querySelectorAll('.anp-acts .anp-ib:first-child');+ var xs = {}; [].forEach.call(copies, function (b) { xs[Math.round(b.getBoundingClientRect().x)] = 1; });+ var aligned = Object.keys(xs).length <= 1;+ if (!aligned) ok = false;+ rows.push({ what: 'copy icons in one column', want: '1 x-position', got: Object.keys(xs).length + ' x-positions', pass: aligned });+ var cols = cs.gridTemplateColumns.split(' ').filter(Boolean).length;+ rows.push({ what: 'columns at this width', want: panel.getBoundingClientRect().width >= TWO_COL_MIN ? '2' : '1',+ got: String(cols), pass: true });+ /* THE GUTTER ROW. Four apps measured at the SAME 744px pane reported panel widths of+ * 734, 735, 702 and 694, because each page put a different amount of its own padding+ * around the mount point. 694 is under the 700 threshold, so that app rendered ONE+ * column while the other three rendered TWO, at an identical pane width. The panel was+ * conformant in all four; the PAGE around it was not. The fleet gutter is 16px a side,+ * so the panel's border box is the pane minus 32. */+ /* Measure against the panel's OWN CONTAINING BLOCK, never documentElement.+ * The first version compared to documentElement.clientWidth, which silently assumes the+ * PAGE scrolls. An app-shell dashboard (position:fixed shell, 44px header, scroller on an+ * inner .app-body) has no document scrollbar, so clientWidth is the full pane and the+ * inner scrollbar is charged to content instead: a correctly built app-shell page fails+ * by exactly one scrollbar width, and only pages that happen to scroll at document level+ * pass. Since app-shell is the prescribed structure, the check was failing the apps that+ * followed the skill. The containing block is scrollbar-agnostic and still catches the+ * real defect, which is a page adding its own padding around the mount point.+ * (parts-search, 2026-08-25, from reading the implementation rather than the prose.) */+ /* Measure BOTH INSETS AGAINST THE PANE, and never against a named element.+ * Two earlier versions asked the wrong element how wide the world is. documentElement+ * assumes the page scrolls, and fails every app-shell dashboard by one scrollbar width.+ * The mount div assumes the 16px lives on the mount div, and reds a visually perfect page+ * that puts the gutter on a shared outer container instead. Both encode a STRUCTURE the+ * fleet was never promised. What the contract actually says is where the panel's EDGES+ * land, so measure exactly that: find the nearest scrolling ancestor (or the document),+ * and check the panel sits 16px inside its client box on both sides. Structure-agnostic:+ * it does not care which element carries the padding, or how many wrappers sit between.+ * (pup found the mount-div version would red an outer-container page, before six apps+ * adopted the pattern I had recommended; parts-search found the documentElement one.) */+ var sc = panel.parentElement;+ while (sc && sc !== document.body) {+ /* A DECLARED scroller is the pane whether or not it is scrolling RIGHT NOW. Requiring+ * it to overflow was a bug with a nastier shape than the two before it: with+ * scrollbar-gutter:stable (which this pack mandates, so results arriving cause no+ * reflow) the track is reserved even when nothing overflows, so the loop walked PAST+ * the real pane, fell back to documentElement, and reported the right inset over by+ * one scrollbar width. The SAME PAGE passed when populated and failed when empty, and+ * empty is a dashboard's first-load state. Predicted from a code read by parts-search+ * and confirmed here: 16 left / 31 right, exact. */+ var so = getComputedStyle(sc).overflowY;+ if (so === 'auto' || so === 'scroll') break;+ sc = sc.parentElement;+ }+ var paneLeft, paneW;+ if (sc && sc !== document.body) { paneLeft = sc.getBoundingClientRect().left; paneW = sc.clientWidth; }+ else { paneLeft = 0; paneW = document.documentElement.clientWidth; }+ var pr = panel.getBoundingClientRect();+ var insetL = Math.round(pr.left - paneLeft);+ var insetR = Math.round(paneLeft + paneW - pr.right);+ var gutterOk = Math.abs(insetL - 16) <= 2 && Math.abs(insetR - 16) <= 2;+ if (!gutterOk) ok = false;+ rows.push({ what: 'panel gutter', want: '16px inset both sides',+ got: insetL + 'px left, ' + insetR + 'px right', pass: gutterOk });+ /* THE LEFT-EDGE ROW closes the hole the gutter row cannot see. The gutter row measures+ * against whatever element the app mounts INTO, so an app that mounts into a wrapper+ * which itself carries padding passes while sitting inset from its neighbours, and the+ * fleet mismatch comes back in a form the check is blind to. This asserts the panel's+ * left edge against the PANE, which covers the whole ancestor chain in one number.+ * (parts-search, 2026-08-25, reasoning about what the new yardstick still could not see.) */+ /* PHRASE HEADROOM AS A ROW, not a console warning nobody reads: "does not truncate TODAY"+ * and "has room" are different claims.+ * MEASURE need AGAINST AN OFFSCREEN nowrap PROBE. Do NOT use clientWidth - scrollWidth:+ * for a block span with overflow:hidden, scrollWidth is max(clientWidth, content), so that+ * expression SATURATES AT 0 for anything that fits, whether it fits by 1px or by 180px. It+ * still goes negative correctly when text overflows, so it looks like a working metric.+ * Two apps independently reported "0px headroom, one character from truncating" off it;+ * remeasured with this probe, neither had less than 141px of slack. A metric that can only+ * return one value on success is not evidence of success. */+ var spans = [].slice.call(panel.querySelectorAll('.anp-phrase span'));+ if (spans.length) {+ var probe = document.createElement('span');+ probe.style.cssText = 'position:absolute;visibility:hidden;white-space:nowrap;font:' ++ getComputedStyle(spans[0]).font;+ document.body.appendChild(probe);+ var worst = 0, worstTxt = '', tight = 0;+ spans.forEach(function (sp) {+ probe.textContent = sp.textContent;+ var need = Math.ceil(probe.getBoundingClientRect().width);+ var have = Math.round(sp.getBoundingClientRect().width);+ var slack = have - need;+ if (worstTxt === '' || slack < worst) { worst = slack; worstTxt = sp.textContent.trim().slice(0, 40); }+ if (slack < 8) tight++;+ });+ probe.remove();+ var headOk = worst >= 8;+ if (!headOk) ok = false;+ rows.push({ what: 'phrase headroom', want: 'at least 8px of slack on every phrase',+ got: worst + 'px on ' + JSON.stringify(worstTxt) + (tight > 1 ? ' (' + tight + ' tight)' : ''),+ pass: headOk });+ }+ var fam = getComputedStyle(panel.querySelector('.anp-phrase span') || panel).fontFamily;+ var famOk = /Satoshi/.test(fam);+ if (!famOk) ok = false;+ rows.push({ what: 'phrase typeface', want: 'Satoshi', got: fam.split(',')[0], pass: famOk });+ return { ok: ok, rows: rows };+ }++ /* checkSend(apiBase) - prove the send path EXISTS, without sending anything.+ * gate() fails OPEN on purpose, so a broken probe cannot block a user from sending. The+ * consequence, which kicad found in itself: A MISSING GATE READS AS AN ALLOWED GATE. Its+ * /updater/status route had been lost in a rebuild, the gate got HTML, caught, and returned+ * open, so the version gate had been silently no-opping. Nothing anywhere said so.+ * This makes that visible. It never posts a prompt: the run-prompt route is probed with+ * OPTIONS, so it can report "wired" without creating a thread in anyone's list. */+ function checkSend(base) {+ base = base == null ? '' : base;+ var rows = [], ok = true;+ function add(what, want, got, pass) { rows.push({what: what, want: want, got: got, pass: pass}); if (!pass) ok = false; }+ return fetch(base + '/updater/status', {cache: 'no-store'})+ .then(function (r) { return r.text().then(function (t) { return {s: r.status, t: t}; }); })+ .then(function (res) {+ var v = null, parsed = false;+ try { var j = JSON.parse(res.t); parsed = true; v = j && j.updater && j.updater.current_version; } catch (e) {}+ add('gate route wired', '200 with JSON', res.s + (parsed ? ' JSON' : ' NOT JSON'), res.s === 200 && parsed);+ add('gate reports a version', 'updater.current_version', String(v), !!v);+ if (v) add('version meets the minimum', MIN_API.join('.'), String(v), verGte(verNum(v), MIN_API));+ })+ .catch(function (e) { add('gate route wired', '200 with JSON', 'FETCH FAILED: ' + e.message, false); })+ .then(function () {+ return fetch(base + '/ai-threads/run-prompt', {method: 'OPTIONS'})+ .then(function (r) { add('send route wired', 'not 404', String(r.status), r.status !== 404); })+ .catch(function (e) { add('send route wired', 'not 404', 'FETCH FAILED: ' + e.message, false); });+ })+ .then(function () { return {ok: ok, rows: rows}; });+ }++ root.AdomNewbiePanel = { mount: mount, toast: toast, selfCheck: selfCheck, copyText: copyText,+ checkSend: checkSend, version: '2.20.1',+ TWO_COL_MIN: TWO_COL_MIN, PHRASE_BUDGET: PHRASE_BUDGET };+})(typeof window !== 'undefined' ? window : this);
dockbar.jsonadded+45@@ -0,0 +1,45 @@+{+ "dock_manifest": "1.2.0",+ "say": [+ "show me my board in altium 3d",+ "open my schematic in altium",+ "add this wiki part to my altium library",+ "run drc on my altium board",+ "generate gerbers from altium",+ "show me the symbol in altium"+ ],+ "webview": {+ "title": "Altium Dashboard",+ "favicon": "dashboard/ui/dock-altium.svg"+ },+ "health": {+ "url": "/api/status",+ "poll_seconds": 15,+ "timeout_ms": 2000+ },+ "launch": {+ "mode": "container_command",+ "console": "show",+ "steps": [+ {+ "label": "Installing or updating the Altium bridge",+ "run": "sh -c 'PATH=\"$HOME/.local/bin:$PATH\"; adom-wiki pkg update altium-bridge || adom-wiki pkg install adom/altium-bridge; command -v altium-dashboard >/dev/null 2>&1 || { adom-wiki pkg uninstall adom/altium-bridge; adom-wiki pkg install adom/altium-bridge; }'",+ "timeout_seconds": 180+ },+ {+ "label": "Starting the Altium Dashboard",+ "run": "sh -c 'PATH=\"$HOME/.local/bin:$PATH\"; exec altium-dashboard serve --print-url'",+ "await": "url",+ "timeout_seconds": 60+ }+ ],+ "on_ready": "navigate_placeholder",+ "ai": {+ "prompt": "The user just opened the Altium Dashboard from the dock. It shows LED status for every Altium bridge facet (relay, box, bridge, Altium install, licence, resident agent, updates). If any LED is not green, offer to fix it (the /api/status payload carries a _hint per LED naming the exact verb). Everything the bridge does to Altium happens in the background; never steal their foreground, and never touch an Altium window the user is working in without telling them first.",+ "thread": "existing",+ "thread_fallback": "new",+ "submit": "staged",+ "typed": false+ }+ }+}
install.sh+24−2@@ -1,5 +1,6 @@ #!/usr/bin/env bash-# Deploy the altium-bridge USER skills into every agent home this container has.+# Deploy the altium-bridge USER skills into every agent home this container has,+# and install the dock dashboard (`altium-dashboard`) that dockbar.json launches. set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" install_skill() { # <dest-dir> <src-file>@@ -9,4 +10,25 @@ for HOME_DIR in "$HOME/.claude/skills" "$HOME/.codex/skills"; do install_skill "$HOME_DIR/altium-bridge" "$HERE/SKILL.md" install_skill "$HOME_DIR/altium-bridge-plan" "$HERE/skills/altium-bridge-plan/SKILL.md" done-echo "OK: altium-bridge skills installed (altium-bridge, altium-bridge-plan)"++# Dashboard: the pkg ships it, the dock runs `altium-dashboard serve --print-url`.+# (kicad-bridge once shipped a dockbar.json whose command the pkg never installed;+# every user's dock click died with exit 127. Install what we carry.)+APPDIR="$HOME/.local/share/adom/altium-bridge"+BINDIR="$HOME/.local/bin"+mkdir -p "$APPDIR/dashboard/ui" "$BINDIR"+cp -f "$HERE/dashboard/altium_dashboard.py" "$APPDIR/dashboard/altium_dashboard.py"+for asset in index.html altium-logo.png dock-altium.svg newbie-panel.js; do+ [ -f "$HERE/dashboard/ui/$asset" ] && cp -f "$HERE/dashboard/ui/$asset" "$APPDIR/dashboard/ui/$asset"+done+if [ -d "$HERE/dashboard/ui/assets" ]; then+ rm -rf "$APPDIR/dashboard/ui/assets"+ cp -R "$HERE/dashboard/ui/assets" "$APPDIR/dashboard/ui/assets"+fi+[ -f "$HERE/dockbar.json" ] && cp -f "$HERE/dockbar.json" "$APPDIR/dockbar.json"+cat > "$BINDIR/altium-dashboard" <<EOF+#!/usr/bin/env bash+exec python3 "$APPDIR/dashboard/altium_dashboard.py" "\$@"+EOF+chmod +x "$BINDIR/altium-dashboard"+echo "OK: altium-bridge skills installed (altium-bridge, altium-bridge-plan) + altium-dashboard"
package.json+8−2@@ -1,6 +1,6 @@ { "slug": "altium-bridge",- "version": "0.1.0",+ "version": "0.1.1", "type": "app", "description": "Skills for your container so your AI knows how to drive the Altium Designer bridge (altium_* verbs). The bridge runtime itself is the release zip; Adom Bridge loads that.", "tags": [@@ -26,7 +26,13 @@ "package.json", "install.sh", "uninstall.sh",- "skills/altium-bridge-plan/SKILL.md"+ "skills/altium-bridge-plan/SKILL.md",+ "dockbar.json",+ "dashboard/altium_dashboard.py",+ "dashboard/ui/index.html",+ "dashboard/ui/newbie-panel.js",+ "dashboard/ui/altium-logo.png",+ "dashboard/ui/dock-altium.svg" ], "discovery_triggers": [ "open my design in altium",
page.json+8−2@@ -4,7 +4,7 @@ "type": "app", "title": "Altium - the Altium Bridge", "brief": "Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.",- "version": "0.1.0",+ "version": "0.1.1", "hero": { "type": "image", "path": "screenshots/hero.png"@@ -111,6 +111,12 @@ "package.json", "install.sh", "uninstall.sh",- "skills/altium-bridge-plan/SKILL.md"+ "skills/altium-bridge-plan/SKILL.md",+ "dockbar.json",+ "dashboard/altium_dashboard.py",+ "dashboard/ui/index.html",+ "dashboard/ui/newbie-panel.js",+ "dashboard/ui/altium-logo.png",+ "dashboard/ui/dock-altium.svg" ] }
uninstall.sh+3−1@@ -3,4 +3,6 @@ set -euo pipefail for HOME_DIR in "$HOME/.claude/skills" "$HOME/.codex/skills"; do rm -rf "$HOME_DIR/altium-bridge" "$HOME_DIR/altium-bridge-plan" done-echo "OK: altium-bridge skills removed"+rm -rf "$HOME/.local/share/adom/altium-bridge"+rm -f "$HOME/.local/bin/altium-dashboard"+echo "OK: altium-bridge skills + dashboard removed"