← Commit history

docs: user skills + README catch up with 0.2.35-0.2.38 (modal orphan, never half-answer a dialog, cleanup_libs/set_params/add_body/extract_intlib/schematic_data, launch hands the window back); battery skip list 9 -> 1 documented; dev/test/PUBLISHING now say this bridge has more than one publisher - clone before you edit

John Lauer ·4b57946847 ·1mo ago ·parent b2b5364
8 files changed +132−3
CHANGELOG.md+7
@@ -1,5 +1,12 @@ # Changelog +## Unreleased (docs, 2026-09-21)+0.2.39 was promoted to the PUBLIC tier (John Lauer's call and his words; public had been on 0.2.9, so 0.2.13-0.2.39 all landed at once), then re-verified by installing from the public manifest as a stranger would and running the full battery against that artifact: 121 pass, 0 fail, 1 safety skip.++- Documented in the user skills what 0.2.35-0.2.38 actually changed: the modal orphan and the never-half-answer-a-dialog rule (altium-instances, altium-licensing, README), `cleanup_libs` keeping a non-empty stray, `set_params`/`add_body` refusing an ambiguous empty name, `extract_intlib`'s `searchedUnder` (altium-libraries), `schematic_data {params:true}` (altium-boards), and `altium_launch` handing the user's window back (README).+- The battery's skip list is down from nine verbs to one. Eight were skipped for reasons that did not survive a dedicated test box; each now runs and proves its named refusal. Only `a365_logout` stays out, because it really does sign the user out. The test skill says what each one does and what to check before adding a tenth skip.+- The dev, test and publishing docs now say that this bridge has more than one publisher: clone the page before editing, push only what you changed, and read a tier before assuming an older version is a bug rather than a deliberate hold.+ ## 0.2.41 (2026-09-21) From the expired-licence incident on the 0.2.40 verification run (Colby's Develop entitlement lapsed mid-test): 
PUBLISHING.md+14
@@ -118,3 +118,17 @@ Author seat is released. - `adom-wiki release list` shows the release with its zip attached. - After a promote: `adom-wiki repo show adom/altium-bridge adom-bridge-altium-manifest.json`   shows the promoted version and the sha256 of the archived manifest.+- Then check it the way a STRANGER would, with no bearer token: fetch the public manifest and+  download the zip anonymously, confirm the sha matches, and install from that manifest URL on a+  real box before you call the promotion done.++## Someone else may be publishing this bridge too++Colby ships this bridge in parallel. **Clone the page before you edit** (`adom-wiki repo clone+adom/altium-bridge <dir>`), work in that tree, and push only the files you changed — publishing from+a stale checkout reverts whatever shipped while you were working. Before touching a tier, read+`adom-wiki repo log adom/altium-bridge` and the insiders `version.json`: an insiders tier serving an+OLDER version than the newest release may be a deliberate hold, not a fault. `--rollback` moves the+PUBLIC tier only; an insiders hold is a direct push of `altium-bridge/version.json` on the `insiders`+page. To live-test a held version without disturbing the tier, push a SEPARATE manifest file (a+distinct name), install from it, and remove it when you are done.
README.md+19
@@ -145,6 +145,10 @@ agent raise a real DelphiScript runtime error and answers `netWorks: true` when Altium's error box, stopped the halted script itself and left no dialog behind. Any verb that hits a script error returns `errorCode: agent_script_error` with `errorShot` (a PNG of Altium's message) instead of timing out. +**Lifecycle** — `launch` (hands the user's foreground window back unless `background:false`),+`close` (polite, releases the licence seat; `discardChanges:true` answers the save prompt in full+and escapes a wedged instance), `instances`, `install`, `uninstall`+ **Surfaces** — `show_project`, `show_schematic`, `show_2d_board`, `show_3d_board`, `show_library`, `show_symbol`, `show_footprint`, `show_3d_chip`, `open_document`, `close_document` (`path`; closes one open document through its handle, never saves), `state`, `demo`, `animate_view` (camera/zoom@@ -287,6 +291,21 @@ save-a-copy warning), engine-halting identifiers are catalogued in the dev skill that could draw one scans Altium's child windows afterwards and attaches a frame if it finds any. A dialog the user sees is logged as a defect, not handled. +**And never a half-answered one (from 0.2.35).** A dialog left mid-exchange is worse than one left+up. Altium's close-time "Save None" does not dismiss *Confirm Save Locally* — it unticks the+documents and renames the same window to *Confirm Not Saving*, which OK then commits; pressing only+the first half left Altium sitting modal. Because Windows disables a process's other top-level+windows for the duration of a modal, that state presents as a **disabled main window with nothing on+screen to answer**: no menu, no Alt+F4, no `-R` hand-off reaches it, and it keeps the licence seat.+`close` and `agent_recover` detect it (`errorCode: altium_modal_orphan`) and+`close {discardChanges:true}` ends it the way Altium's own Exit does — a posted `WM_QUIT`, which+RELEASES the seat where a `taskkill` would strand it.++**Launching does not cost the user their window (from 0.2.37).** Altium foregrounds itself at+startup and offers no switch for it, so `altium_launch` remembers the window the user was in and+hands it back once Altium settles (`foregroundReturnedTo`, `windowEtiquette: background`). Pass+`background:false` when the user asked to watch Altium come up.+ ## Lineage  The bridge consolidates Barrett Land's summer-2026 Altium work — his earlier repos
dev-skills/altium-bridge-dev/SKILL.md+14
@@ -93,6 +93,20 @@ seconds with `errorShot`, and `altium_status` must then read `ready: true` with Test new agent commands on a SECOND Altium build before promoting; one machine proved nothing about 26.9.1. +## You are not the only one shipping this bridge++Colby ships altium-bridge in parallel from his own machine. On 2026-09-21 he published 0.2.40 and+0.2.41 on top of a 0.2.39 that had been pushed an hour earlier. Nothing was lost because he pulled+first — do the same: `adom-wiki repo clone adom/altium-bridge <dir>` before editing, work in THAT+tree, and `repo push --files <only what you changed>`. Publishing from a stale checkout silently+reverts whatever shipped while you were working.++Read the state before you act on it: `adom-wiki repo log adom/altium-bridge` and the insiders+`version.json`. A tier that serves an OLDER version than the newest release can be a deliberate+hold — he rolled insiders back to 0.2.39 himself, keeping 0.2.40/0.2.41 out until a licensed box+could test them. `scripts/releases/<ver>.json` is a LOCAL build artifact, so your tree only holds+archives for versions you shipped; `--promote` needs the archive for the version being promoted.+ ## The modal orphan: Altium wedged with no dialog on screen (measured 2026-09-21, AD 26.10.1)  The worst state this bridge has produced. On arav-rog an Altium held the single On-Demand licence
dev-skills/altium-bridge-test/SKILL.md+37−1
@@ -27,7 +27,7 @@ Exit code 0 only when there are no FAILs and no uncovered verbs. |---|---| | PASS ok | the verb answered `success:true` (and its `check`, when the entry has one: pin counts, 9 via holes, 3 parts placed, `netWorks:true`) | | PASS refused | the verb answered `success:false` WITH a named reason. For verbs whose real inputs the battery does not have (an OutJob, an IntLib, an Altium 365 token, a project) a fast, named refusal is the contract under test |-| SKIP | never run, reason printed. Only verbs that would harm the machine or the user's account: `foreground`, `demo`, `setup_trial`, `connect_workspace`, `license`, `install_extension`, `a365_configure`, `a365_oauth_begin`, `a365_logout` |+| SKIP | never run, reason printed. Exactly ONE verb: `a365_logout`, which really does sign the user out of Altium 365 on a configured machine and has no read-only mode | | FAIL | a timeout or transport error (the bridge wedged), `success:false` with NO reason, the wrong outcome for the entry, or **a dialog left on the user's screen after the verb** (checked after every verb with `altium_check_dialogs`) |  The dialog check is the point of the whole thing: John's rule is that a dialog the user has to@@ -48,6 +48,28 @@ dismiss is a defect. A verb that "worked" and left a box up fails the battery. - `selftest_script_error` flashes one Altium error box for under a second. Do not run the battery   while someone is mid-edit in Altium on that machine. +## The skip list is one verb, and stays that way++It used to hold nine. They were skipped when the only Altium lived on a machine someone was working+on, and the reasons did not survive contact with a dedicated test box (John, 2026-09-21: *"none of+those verbs seem unsafe to me?"* — he was right about eight of them). Each one either reads state or+refuses by name BEFORE it touches anything, and proving that refusal is the whole point of a battery:++| Verb | What it actually does here |+|---|---|+| `demo` | `stage:0` is a BACKGROUND beat with a screenshot. Only `demo {live:true}` takes the foreground, and that is not run |+| `foreground` | raises Altium on a box the runner already proved idle — run it last in phase 5 |+| `setup_trial` | with Altium CLOSED (phase 0) it reports the next action; the stage that could start a trial needs a signed-in, UNLICENSED Altium running |+| `connect_workspace` | with Altium closed it is a clean `altium_not_running` refusal, so the workspace picker never opens |+| `license` | an unknown action proves the dispatch and the named refusal; `use_license` / `start_trial` / `sign_out` are deliberately NOT exercised — the seat is shared and single |+| `install_extension` | Altium is running by phase 2, and the verb refuses in that state (it holds AdomBridge.dll open) |+| `a365_configure` | validates before it writes: no workspace = a named refusal, config untouched |+| `a365_oauth_begin` | validates before it opens anything: no workspace configured = `a365_workspace_required` |++Before you add a tenth skip, check whether the verb has a read-only mode, a safe stage, or a state in+which it refuses by name. `install` (stage `preflight`), `uninstall` (`dryRun`) and `ensure_licensed`+(defaults) all earn their coverage that way. A skip is an admission the contract is untested.+ ## Adding a verb  Add the verb to `src/bridge.json` + `verbs.rs` as usual, then add ONE entry to `matrix()` in the@@ -70,3 +92,17 @@ writer changes, so the battery also covers that lane. 2. Battery green on a machine that is NOT the one the change was written on. 3. Ideally once on a from-scratch Windows install (the install lane is the first thing a public    user touches and the battery assumes Altium is already there).+4. **Then run it once more against the PROMOTED artifact**, installed from the PUBLIC manifest the+   way a new user would (`bridge_install {manifestUrl: ".../adom-bridge-altium-manifest.json"}`).+   Testing the insiders zip proves the build; only this proves the thing strangers actually download.+   Done for 0.2.39 on 2026-09-21: 121 pass / 0 fail / 1 safety skip.++**Promotion itself is never the AI's call.** `release.py --promote` refuses without the human's own+words (`--attest`, 8+ chars, no placeholder). Quote them verbatim — they are the audit trail for why+the public tier moved.++**Someone else may be shipping this bridge too.** Colby works on it in parallel. `adom-wiki repo+clone` the page before editing and push only the files you changed, or you will revert releases that+are newer than your tree. Check `adom-wiki repo log adom/altium-bridge` and the insiders+`version.json` first: a tier can be deliberately rolled BACK (he held 0.2.40/0.2.41 there on+2026-09-21 for want of a licensed box), and that is not a bug to "fix".
skills/altium-boards/SKILL.md+6−2
@@ -126,7 +126,11 @@ All of these need a licensed Altium and read the current document unless a path `list_designators`, `component_data {designator?}`, `component_pins {designator}`, `list_nets`, `net_connections {net?}` (pads as DES.PIN with coordinates + per-net span), `layers` / `copper_layers`, `read_rules`, `footprint_primitives {designator}`, `set_layer_visibility-{layerId, displayed}`; `schematic_data {designator?, pins?}` on the current sheet. They read the-CURRENT document: opening a .SchDoc makes board verbs answer "no current PCB board" until a+{layerId, displayed}`; `schematic_data {designator?, pins?, params?}` on the current sheet. They read+the CURRENT document: opening a .SchDoc makes board verbs answer "no current PCB board" until a .PcbDoc is opened again — order your calls. Payload is under `data`. Multi-part symbols list once per part (`partId`).++`schematic_data` returns an EMPTY `params[]` unless you ask: pass `params:true` (from 0.2.38 — the+agent always honoured the flag, but the bridge never sent it, so the field came back empty no matter+what). `altium_read_schematic` is still the fuller parameter view.
skills/altium-instances/SKILL.md+20
@@ -80,6 +80,26 @@ Two Altiums can run; only one can be licensed — see [altium-licensing](../alti So a genuinely parallel two-thread test is limited to one licensed instance at a time, and swapping the seat means closing one app politely. Never kill it: that strands the seat. +## Closing an Altium that answers nothing (from 0.2.35)++`altium_close` times out, `altium_agent_start` says `no_heartbeat`, and everything comes back as+*"There is another instance of running, but it is busy at the moment"* — yet `altium_check_dialogs`+is EMPTY. That Altium is wedged inside a modal loop whose dialog is gone: Windows disables a+process's other top-level windows while one of them is modal, so with nothing left on screen to+answer, the app takes no input at all — no menu, no Alt+F4, no `-R` hand-off — and it is still+holding the licence seat.++Both `altium_close` and `altium_agent_recover` name it: `errorCode: altium_modal_orphan`. The way+out is `altium_close {discardChanges:true}`, which ends that instance the way Altium's own Exit does+(a posted `WM_QUIT`) and RELEASES the seat. Never reach for `force:true` — killing the process is+what strands the seat. Unsaved work in that instance is gone either way, so if a document might be+the user's, ask first. Full story in the altium-licensing skill.++**Never leave a dialog half-answered** — that is what creates this state. Altium's close-time+"Save None" does not dismiss the save prompt; it unticks the documents and RENAMES the same window+to "Confirm Not Saving", which OK then commits. A button that CHANGES a dialog rather than closing+it must be followed through in the same pass.+ ## `altium_selftest_script_error` (from 0.2.23)  A script runtime error used to leave Altium parked on a breakpoint with every verb timing out. From 0.2.21 the
skills/altium-libraries/SKILL.md+15
@@ -128,3 +128,18 @@ report the check per variant, and refuse or rotate on disagreement. must not inherit the source MPN), `rename_symbol`, `remove_symbol`, `read_symbol`, `copy_footprint` / `xcopy_footprint` (cross-library) / `rename_footprint` / `remove_footprint`, `cleanup_libs` (kills the factory `Component_1`/`PCBComponent_1` strays).++**`cleanup_libs` only removes an EMPTY stray (from 0.2.38).** It used to delete a SchLib component+named `Component_1` unconditionally — pins, art and all — so a symbol someone had actually drawn+into the factory placeholder was destroyed. Both sides now check the component is empty first and+report `keptNotEmpty` for anything they left alone.++**Name the component you mean (from 0.2.38).** `set_params` and `add_body` used to act on whichever+component the iterator happened to yield FIRST when `symbolName` / `footprintName` was omitted, so+against a multi-part library they silently wrote a footprint, a comment or a 3D body onto the wrong+part. An omitted name now only means "the one component in this library": with more than one they+refuse, `errorCode: name_required`, naming the count. Always pass the name.++**`extract_intlib` reports what it actually extracted (from 0.2.38).** `sources[]` used to list every+`.SchLib`/`.PcbLib` open anywhere in the workspace; it is now scoped to the IntLib's own folder,+which the reply names in `searchedUnder`.