main
Colby Knox 0.2.58 source (not yet released, needs signing): autoroute, layer_stack, rule descriptors, sch_zoom f82b5b5 8h ago

Changelog

0.2.58 (2026-10-21)

  • altium_autoroute: Altium's Situs autorouter on the current board.
    • It picks a strategy and keeps existing routing locked by default; Situs ripped it up with the dialog's default.
    • It answers the unconnected count and DRC errors before and after, and doesn't save unless asked.
    • Measured: one ripped-up net went from 4 unconnected to 1 in 14 s, with the existing copper untouched.
  • altium_layer_stack: copper layers in order, with copper thickness and the dielectric under each (height, Dk, material) and the total thickness.
    • It uses Board.LayerStack_V7; on AD 26 the classic LayerStack.FirstLayer halts the script.
    • Measured: the buck reads 2 layers, 36 um copper over 320 um FR-4; the Mini PC example reads 16 layers of TU-883, 2.0 mm.
  • altium_read_rules carries each rule's descriptor, Altium's wording with the values (e.g. Clearance Constraint (Gap=6mil)), so a DRC result can be explained.
  • altium_sch_zoom: selects parts on a sheet, frames them, and returns a frame. It is the schematic half of cross-probing.

0.2.57 (2026-10-21)

Schematic editing on an existing design, and the change order to the board.

  • altium_sch_place_part: places a .SchLib part on an existing sheet at a position, with its library parameters and footprint links (or a named footprint).
  • altium_sch_connect: wire stubs from pins' outer ends, with a net label (kind:"label"), a power port (kind:"power"; GND-like nets get a ground symbol) or nothing. Each connection reports ok or why not.
  • altium_sch_edit: move, moveBy, rotate, rename, re-comment or delete placed parts, in one undo step.
  • altium_annotate: numbers every R?, C?, U? with the lowest free number per prefix across the project. Each part is named by its position, so several R? on one sheet are told apart. dryRun shows the plan.
  • altium_doc_params: reads or sets a sheet's title-block parameters.
  • altium_update_pcb: Design > Update PCB Document.
    • It previews by default: parts to add, and board-only parts the change order would delete.
    • execute:true runs it, and refuses deletions unless allowRemovals:true.
    • It answers the one-time "link parts by designator" prompt on a board built by script (a real click: that dialog's buttons ignore Invoke) and the Information box after it.
    • It presses Execute Changes, waits until every row is done (the agent is parked while the modal is up, so it cannot be the signal), closes, and saves the board. The change order edits it in memory only; a first test lost every change by closing it.
    • Verified live on AD 26.11.1: R9 and R10 placed, annotated and connected on a sheet reached the board with their nets (R9: VOUT/GND, R10: VIN/GND) and survived a close and reopen. A comment-only change on an existing part opens no change order on that build; documented.
  • altium_export_pdf: every sheet as one PDF, plus the board's composite print with board:true. It runs through an OutJob in a throwaway project, so the user's project is untouched. Verified: a 2-page PDF in 14 s.
  • altium_run_outjob (and every verb built on it: manufacturing, STEP, PDF) answers Altium's "PCB Health Check Issues Detected" modal, which otherwise stopped the job indefinitely. It presses Generate Outputs (healthCheck:"stop" cancels) and returns the dialog's text in healthCheck.
  • schematic_data also reports each part's uniqueId.
  • New skill altium-schematic-edit. The main skill opens with an "I want to..." map of the whole bridge, and notes that altium_eval does nothing while the agent is resident.

0.2.56 (2026-10-21)

  • altium_bom: the project's BOM, read straight from Altium.
    • Reads every schematic sheet the .PrjPcb lists, managed device sheets included, with each part's parameters.
    • Merges the board's placement and an optional variant (Not Fitted parts become DNP; alternates are listed).
    • Groups one line per manufacturer part number. It matches parameter names across libraries: MPN = Manufacturer Part Number = PartNumber, plus the Mouser, DigiKey and LCSC spellings.
    • Checks what an order needs: missing MPNs and distributor numbers, one part number on two footprints, board-only and sheet-only parts, DNP, and parts that stay out of a BOM.
    • Writes bom.csv, bom-jlcpcb.csv + cpl-jlcpcb.csv, bom-mouser.csv, bom-digikey.csv and bom.json.
    • Verified on AD 26.11.1: the TPS54202 buck (17 lines in 0.5 s), Altium's Mini PC example (46 sheets, 1046 parts, 132 lines in 19 s) and Bluetooth Sentinel (15 sheets including device sheets).
  • altium_sheet_params writes parameters onto placed parts, finding each designator on whichever sheet holds it, and saves the sheets. It is the BOM write-back.
  • altium-bom (container CLI, installed with the package):
    • read runs altium_bom and saves the result.
    • price makes one adom-parts-search call per line and reports stock against the boards needed, price, lead time, lifecycle and JLCPCB-only parts, plus a cheapest in-stock total.
    • fill writes the missing distributor numbers back onto the sheets.
    • New skill: altium-bom.
  • schematic_data:
    • Also reports each part's kind (ComponentKind), itemGuid, vaultGuid and sourceLibrary, and its description.
    • A workspace-managed part's description is not read: on Altium's own Content Vault parts that call never returned.
    • New paramFilter and paramNamesOnly options read only the values a caller needs; one example IC's three preview-image parameters took minutes to read.
  • The agent's JSON escaping is linear. It appended one character at a time, which is quadratic in DelphiScript; every verb that returns long text gets faster.
  • The altium-wiki-import skill no longer says the Cloud Library cannot export Altium: it can, without distributor numbers, and the skill says how to add them.
  • uninstall.sh also removes the altium-* container commands.

0.2.55 (2026-10-20)

  • The edge rule no longer flags silkscreen (#26). set_design_rules takes edgeClearance from the fab pack, where it means copper to the board edge (KiCad's edge_clearance). When the board had no outline-clearance rule, it created "Adom edge" scoped to everything, so Altium's DRC flagged silkscreen near the edge too, such as AI Flow's contact frames 0.12 mm in. The new rule is scoped OnCopper, and an existing "Adom edge" still scoped All is re-scoped the next time the rules are set. Silkscreen keeps Altium's own silk rules.
  • Verified live on Altium Designer 26.11.1, on two copies of the same saved board with the gap widened to 0.6 mm so silkscreen near the edge is in reach: scope All gave 28 edge violations, OnCopper gave 5 (the copper). Every other rule's count was identical (137 silk-to-mask, 48 silk-to-silk, 8 mask sliver).

0.2.54 (2026-10-20)

  • An Altium board's STEP anchors as a molecule. Altium's STEP export names each component's PRODUCT after its designator but leaves every assembly occurrence unnamed, where KiCad names the occurrence (MP1). step2glb convert --molecule finds the four machine pins by occurrence name, so an Altium molecule board converted molecule_anchored: false (0 MP markers) and its GLB was unanchored. altium_export_step now names each board component's occurrence after its designator (both lanes; occurrences.named in the reply, nameOccurrences: false to keep Altium's file as written). Nested model occurrences and occurrences Altium already named are left alone.
  • altium_routing_state answers errorCode: "no_current_board" and the agent's message when another document (a schematic) is in front, instead of an empty error.
  • Verified: the TPS54202 molecule board's Altium STEP (34 components) converts molecule_anchored: true after the pass; a unit test covers the root/nested/escaped-quote cases.

0.2.53 (2026-10-20)

  • symbol_prims answers the drawn mode's body. bodyRectMil was the first rectangle found, which could belong to any display mode: the TPS54202's 10-mode symbol answered a wider mode's 1200 mil body for its 700 mil drawn body, and a capacitor answered one plate. It is now the union of the current display mode's rectangles (mode 0, the one a sheet draws).
  • Library pins keep their electrical type. altium-symbol-modes carries each KiCad pin type into the spec (a 10th field), and build_symbol_modes sets Altium's pin type from it: input, output, bidirectional (IO), tri-state (HiZ), open collector, open emitter, power in/out (Power), else Passive. Every pin used to be Passive, so Altium's ERC and AI Flow's pin-type check could not tell a power or output pin from a resistor's. read_symbol returns each pin's electrical (0 Input, 1 IO, 2 Output, 3 OpenCollector, 4 Passive, 5 HiZ, 6 OpenEmitter, 7 Power). Live on AD 26.11.1: the TPS54202's GND, SW and VIN read 7, FB and EN 0, BOOT 4.

0.2.52 (2026-10-20)

  • Importing a part again replaces its 3D body instead of stacking a second one. add_body (and the step of import_part, which calls it) added a new body every call and never removed the old one, and normalize_bodies then centred both on the pads: the AMPLH5030S inductor reached a board (2026-10-16) with the old model and the fixed one inside each other, repaired only by remove_footprint, a re-import and PCB:UpdateFootprints. The agent now deletes the footprint's existing 3D bodies before it adds the new one, the way the editor's Edit > Delete does (it selects exactly those bodies and runs PCB:Clear; RemovePCBObject left them in the stored footprint after save), how build_symbol_modes replaces a symbol of the same LibReference. It removes nothing until the new STEP has loaded, so a bad path keeps the old body.
  • The add_body reply says replacedBodies (also at the top level), keptExisting, bodiesBefore, sameNameFootprints and bodiesNow, and its _hint warns when bodies are still stacked or when several footprints share the name (only the first gets the body). import_part's add_body step carries replacedBodies, bodiesNow and sameNameFootprints.
  • New add_body argument keepExisting (default false): add next to the existing bodies, for a deliberate second body.
  • Boards that already place the part keep the old body until PCB:UpdateFootprints.
  • Verified live on Altium Designer 26.11.1: three repeated adds keep 1 body, keepExisting makes 2 and the next plain add makes 1 again, a bad STEP path keeps the old body, and the saved and reopened footprint has 1 body and its 2 pads.

0.2.51 (2026-10-19)

  • IC symbols attach to their box again. altium-symbol-modes (tools/symbol_modes.py, the spec behind build_symbol_modes / import_part) wrote each pin's outer end where Altium's Pin.Location wants the end on the body, so every pin of a 2-column or 4-side symbol floated one pin length (300 mil) off the box with its name in the gap. Pins now start on the body edge in all ten display modes. Small parts drawn from their KiCad symbol (resistors, capacitors, inductors, LEDs) were never affected.
  • dev-skills/altium-bridge-test/test_symbol_modes.py: every pin of every mode must start on its body edge (fails on 0.2.50, passes on 0.2.51).
  • Symbols already built with 0.2.50 or earlier keep the gap until rebuilt: run altium-symbol-modes again and altium_build_symbol_modes (it replaces the symbol), then altium_add_models to put the footprint link and parameters back.

0.2.50 (2026-10-16)

  • Templates are found on every Altium build. build_schematic and export_manufacturing look for Altium's sheet and OutJob templates under the running install's own Public Documents folder (ADDevelop, AD26, ...) instead of AD26 only, so they work on Altium Designer Develop.
  • symbol_mode no longer halts the script on Altium Designer Develop: it referenced an undeclared identifier (NOld). build_symbol_modes reports replaced.
  • Full verb battery green on BOTH builds: Altium Designer Develop (Colby-ROG2) and Altium Designer 26.11.1 (DESKTOP-BAR6S0F), each 145 pass, 0 fail, 1 skip (a365_logout), 0 uncovered. 0.2.49 failed on Develop, which this release fixes; 0.2.50 is the build promoted to public.

0.2.49 (2026-10-16)

  • Altium Designer 26.x with no licence tail reads as licensed (#19): the absent-tail rule covered only the Develop edition's window, so Altium Designer running on a Develop licence read Unknown. The Use License pixel fallback now works through the bridge (it takes its own screenshot instead of needing a shotId).
  • No X2 -R helper is spawned while a non-script dialog blocks Altium, and recover stops and names the dialog: one save prompt had turned into 15 minutes of "another instance is busy" notices.
  • build_board and build_schematic never close a project over documents they did not make: the project reopen and rewrite read the project's members from Altium itself, save them before closing, and never close documents the project file does not list.
  • The verb battery covers all 146 verbs (phase 3.5 runs the AI Flow board lane) and refuses to start over a running Altium. Run twice on Altium Designer 26.11.1: 145 pass, 0 fail, 1 skip (a365_logout), selftest_script_error netWorks true.

0.2.48 (2026-10-16)

  • list_library and the show_* surfaces refuse a document named under an argument they do not read, instead of opening the bundled demo library; every reply says demoFallback (#20).
  • schematic_data refuses a schematic library as the current sheet and takes filePath (#21).
  • build_board into a project that already lists the board reopens the project, so the board has one entry and one view (#23).
  • read_symbol returns the symbol's model links (#24).
  • release.py checks and pushes page.json and bridge-rs/src/main.rs with the other version stamps (#22).

0.2.47 (2026-10-16)

  • The AI Flow Altium lane's verbs. Live board edits: routing_state, route_net, move_parts, place_vias, remove_items, ripup_nets. Pours with a connect style per pour, solid joints, cutouts and island/neck options, repoured outside the script engine (scoped rules apply only there).
  • set_design_rules, build_board, and build_schematic on Altium's own sheet template inside its project, compiled and checked pin for pin.
  • project_nets, export_manufacturing (the project's own OutJob), silk_state and apply_silkscreen, select_net, zoom_to, add_rule.
  • Fixes: passive symbols keep their standard drawing; route_net places no via inside a same-net through-hole pad; run_drc answers the repour prompt; agent start closes old script projects; verb timeouts match the docs (release.py now refuses a bridge.json timeout shorter than a verb's documented one).

0.2.46 (2026-10-15)

  • Headless STEP export works (adom/altium-bridge PR #3, Aditya Angajala). altium_export_step no longer needs Barrett's .NET extension, which never loads into a running AD 26. The default lane outjob writes an ExportSTEP OutJob and a throwaway project pointing at the board into %TEMP%, runs it through the existing OutJob path, copies the .step to output, then closes both documents and removes the folder, so the user's own project is never opened or changed. Measured on AD 26.9.1: a 30 x 30 mm board in 3.5 s with no dialog.
  • New arguments: holes (default true), suffixType none/board/custom/id plus suffix, folded, singlePart, and the raw Altium options copperOption, modelsOption, componentsOption. The reply lists products[], the STEP part names: Altium names parts by DESIGNATOR only.
  • Named failures: pcb_not_found, bad_args, step_not_written (with the OutJob reply and its dialogs), copy_failed; the failure replies name tempDir, which is kept for inspection. lane:"extension" keeps the old path.

0.2.45 (2026-10-15)

First live run since 0.2.39 on a licensed Altium (Altium Designer Develop, Colby-ROG2). It verified the held 0.2.40-0.2.44 features and fixed what the run and two new issues found.

  • A save into a missing folder no longer fails silently. altium_test_board {pcbPath} and altium_place_on_sch {schPath} answered with the path while Altium's save wrote nothing (the folder did not exist), so the next "append" found no board and started a new one. The bridge now creates the folder first and checks the file on disk: saved:true, or errorCode: not_saved. A document that is open but not yet saved now counts as existing for append.
  • Appending to a schematic grows the sheet (sheetGrown), so the added parts stay inside the drawing frame instead of landing in the gray past its right edge.
  • altium_board_shape proves its own result (adom/altium-bridge#16). It, pcb_report {want:'board'} and read_board's outline now read the outline's vertices: BoardOutline.BoundingRectangle is cached and kept the 6000 x 4000 mil default after a reshape, on AD 26.9.1 and Altium Develop alike. New mechOutline:true draws the rectangle on Mechanical 1 so the fab Gerber has an outline (the board shape alone is plotted into none).
  • altium_test_board parts carry their own name and footprint (adom/altium-bridge#17). The part name is the component's Comment, so it moves with the part (loose overlay text stayed behind and piled up in the silkscreen Gerber), and Pattern is the footprint name (it was empty, so BOM, STEP naming and footprint comparison saw nothing).
  • New altium_uninstall_library and altium_list_installed_libraries (adom/altium-bridge#14). Uninstall matches libraryName (file stem: the SchLib and the PcbLib together) or an exact libraryPath, closes any open document for the library first, never touches the files, and answers notInstalled:true when nothing matches. Verified across a clean restart. It also cleared two dead "UI" entries left behind when 0.2.9 flattened the bundle folder.
  • The "X2 / Continue execution?" script box is classified (script_runtime_error, safe action Cancel) instead of scanning as unknown: its text is painted, so it is only in the photo.
  • altium_panel's timeout now says why: on Altium Develop a UI Automation search of the main window takes over 20 s, and no Altium process switches a docked tab; opening a library already brings its panel to the front.
  • Verified live, no code change: append on board and sheet (#11), focus kept on the user's window through document opens and agent restarts (#12), altium_panel answers inside its budget and the next verb is not delayed (#13).

0.2.44 (2026-09-29)

  • The Altium extension DLL is signed too. extension/AdomBridge/AdomBridge.dll now carries the same Adom Industries, Inc. signature as altium-bridge.exe: Windows Smart App Control checks the DLLs a program loads, not only the exe it starts, so an unsigned plugin could be refused on a PC where it is on. The release script signs and verifies every exe and DLL it ships and refuses to package any that is unsigned. Code otherwise identical to 0.2.43.

0.2.43 (2026-09-29)

  • The exe is signed. altium-bridge.exe now carries an Authenticode signature from Adom Industries, Inc., with a timestamp, so Windows 11 Smart App Control no longer blocks it ("Part of this app has been blocked"). The release script signs every exe it ships as the last step before packaging, verifies the signature on Windows and again after the file comes back, and refuses to package an unsigned exe.
  • Same code as 0.2.42 otherwise (bounded altium_panel, altium_demo error reporting), which is still waiting for its licensed test run.

0.2.42 (2026-09-23), STAGED for the licensed battery, not on any tier

Fixes the two defects John's licensed live test of the held 0.2.41 found (adom/altium-bridge#13, arav-rog, AD 26.10.1).

  • altium_panel answers inside 24 s with a named result, every time. It was not a loop that never ended: it made up to five sequential UIA searches (read, select, read, click, read) with 10+15+10+15+10 s timeouts, exactly ab's 60 s budget, and each search of Altium's large window tree ran into its timeout, so the verb ended in ab's generic timed_out with the abandoned searches still running (the likely cause of the collateral run_drc timeout that followed it in the battery). Now one search reads the tab's existence and selection together, then at most one select and one verify, inside a 24 s budget; the answers are already, ui_select, panel_not_found, panel_search_timeout (with the stage), ui_select_gated, panel_not_selected. The click fallback is gone (a click does not switch panel tabs), and when ab's select falls back to its raising SendInput click the reply says lane: ui_select_raised instead of hiding a foreground steal.
  • altium_demo carries the failing beat's own error. A failed beat came back as a bare success:false (John saw it on Altium Designer Develop). The reason was real and discarded: the tour opens Altium's bundled AD26 example projects, which a Develop install does not have (file not found). The reply now carries the beat's errorCode/error/_hint, names this case demo_examples_missing, and falls back to demo_beat_failed.

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):

  • The expired-licence box is catalogued ("has expired or been deactivated", category licence_expired): it scanned as unknown while it blocked every verb and wedged the close into a modal orphan that survived even WM_QUIT. The entry records the detection gap it exposed: Develop shows NO title licence tail in this state either, so the absent-tail "Licensed" inference reads wrong while this box is up.
  • Lone-OK boxes whose OK refuses UIA are dismissed natively. This box's OK exposed no InvokePattern (button_not_pressed in 19 ms); its titlebar X performs the same acknowledgement, so press now falls back to a POSTED WM_CLOSE on the dialog - only ever for an OK, never for a button that answers a question.

0.2.40 (2026-09-21)

The two asks still open from John's ToF session (adom/altium-bridge#11 and #12), on top of what 0.2.13-0.2.39 already covered.

  • Append to an EXISTING board or sheet (#11). altium_test_board {pcbPath} and altium_place_on_sch {schPath} now OPEN the document when the path already exists and ADD the parts: placed to the right of the current ones (board: right of the rightmost anchor, advancing by 1.5x each footprint's measured pad extent; sheet: 4000 mil right of the rightmost symbol, in line with the existing row), designators continuing from the existing count (U4, U5, ...). The board keeps its outline and layers (wMm/hMm ignored; resize with altium_board_shape); the sheet keeps its size and title block. Replies carry appended and existingParts. A new path still creates, exactly as before.
  • Foreground etiquette on EVERY driving verb (#12). The dispatch chokepoint now remembers the window the user is in before each driving verb and, when Altium's process holds the foreground afterwards where it did not before, hands the user's window straight back - the open/close_document, place_on_sch, install_library, agent start/stop and run_command steals John measured are all covered by the one wrapper. Replies say so: windowEtiquette: background, plus foregroundReturnedTo when a steal was reclaimed. A user deliberately on some OTHER window mid-verb is left where they went; verbs whose point is the screen (foreground, launch, demo, install, close) are exempt.
  • New verb altium_panel {name, ensureId?} (#12's ganged-up view). Activates a docked panel TAB (SCH Library, PCB Library, Components, ...) in the background: UIA SelectionItemPattern on the dock tab, InvokePattern fallback, verified by reading the tab's own selection state - no server process can do this (Client:CustomizeResources only ensures the panel exists; a plain click does not switch tabs, measured 2026-09-02). Honest failure modes: ui_select_gated when ab's shell grant is off (adom/adom-bridge#123), panel_not_selected with the attempted lanes otherwise.

0.2.39 (2026-09-21)

  • selftest_script_error gets an honest timeout budget (60 s → 180 s). It is the slowest verb in the catalog by design — it halts the engine, waits for the halt to be noticed, recovers it and verifies by restarting the agent — and Altium Designer Develop parks SILENTLY, so the halt is only seen when the wait expires: measured 18 s on one run and over 60 s on the next, on the same machine. The bridge was cutting its own safety-net test off at 60 s and reporting a timeout, which reads as "the net is broken" when it is not. A timeout here now means something real.

0.2.38 (2026-09-21)

Correctness pass over the findings from the code review, each one verified in the source before it was touched.

  • cleanup_libs no longer deletes a symbol someone drew. It removed a SchLib component named Component_1 unconditionally, pins and all; the PcbLib side had always checked the footprint was empty first. Now both sides only remove the empty factory stray, and the reply reports keptNotEmpty.
  • set_params and add_body refuse an ambiguous empty name (errorCode: name_required, with the count). With no name they took whichever component the iterator yielded first, so against a multi-symbol library they silently wrote a footprint, a comment or a 3D body onto an arbitrary part. An omitted name now only means "the one component in here".
  • extract_intlib reports the libraries it actually extracted. sources[] listed every .SchLib/.PcbLib open anywhere in the workspace; it is now scoped to the IntLib's own folder, and says so in searchedUnder.
  • schematic_data {params:true} now reaches the agent. The agent has always honoured it, but the dispatch never sent the flag, so params[] came back empty and the docs had to apologise for it.
  • symbol_prims and import_kicad gate on the licence like every other authoring verb, instead of failing later and less clearly.
  • setup_trial no longer says "Launching it next" at a stage where it launches nothing — it names the next action, which is its whole contract.
  • Reviewed and found correct, no change: run_outjob opens the .OutJob with ObjectKind=Document (the object kind for the RUN step is separate, and is passed), and run_drc promises nothing it does not return.

0.2.37 (2026-09-21)

  • altium_launch hands the user's window back. Altium foregrounds itself at startup and offers no switch for it, so the bridge now remembers the window the user was working in and restores it once Altium has settled (foregroundReturnedTo, windowEtiquette: background). background:false opts out when the user asked to watch Altium come up. John, twice in two days: "dude u keep forcing altium in front of me while i'm typing".
  • Full battery GREEN ON BOTH BUILDS: 122 verbs, 113 pass, 0 fail, 9 skipped for safety, on Altium Designer 26.10.1 (arav-rog) and Altium Designer Develop (ConfRoomROG) — close included, for the first time. Both machines were left with no Altium running and no dialog on screen.

0.2.36 (2026-09-21)

Fixes the last failing verb in the battery, close, and with it an overnight hostage situation: an Altium on the test box held the single licence seat for eight hours because nothing could reach it.

  • The close-time save prompt is answered in full. "Save None" does not dismiss Altium's "Confirm Save Locally for (N) Modified Documents": it unticks the documents and RENAMES the same window to "Confirm Not Saving (N) Modified Documents", which OK then commits. close {discardChanges:true} pressed only the first half and went back to polling, leaving Altium sitting modal.
  • A modal Altium disables its own main window — that is how a half-answered prompt strands it. When the dialog is then gone, the app is inside a modal loop with nothing on screen to answer: no menu, no Alt+F4, no WM_CLOSE, and every -R hand-off bounces off "There is another instance ... but it is busy". close and agent_recover now DETECT this (errorCode: altium_modal_orphan) instead of timing out or firing helper processes at it.
  • A graceful escape that keeps the seat. close {discardChanges:true} ends a stuck instance with a posted WM_QUIT — Altium's own Application.Terminate, which runs its shutdown and RELEASES the On-Demand seat (measured: exit in under 8 s, the next launch licensed). A taskkill strands the seat; this does not.
  • close no longer runs the breakpoint recovery on every call. It asked "is a loop alive?" AFTER tearing the loop down, so the answer was always no: every close paid ~10 s of -R hand-offs, and each one that failed spawned a helper X2. It now asks before the teardown.
  • A swallowed bootstrap is re-sent, not reported as a failure. Altium can accept the RunScript hand-off and then drop it while it is busy with its own startup or unloading the script it was just told to stop; the agent never heartbeats and a healthy engine looked broken (no_heartbeat on the first start after a launch, and on the verifying start inside recover). agent_start now sends it once more before answering, and says so with resentBootstrap. A dialog on screen or a modal orphan is still reported immediately — those are real answers, never retried over.
  • agent_recover's success hint no longer claims "a fresh agent loop is running" when it was asked not to start one.

0.2.34 (2026-09-21)

Third full battery on Altium Designer 26.10.1: 122 verbs, 112 pass, 1 fail (close), 9 skipped for safety.

  • The reaper photographs every box before pressing it, not only ones titled "Error". An "Information" box explaining why Altium refused to quit was pressed away five times in a row, unread. altium_dialog_log {shots:true} returns the last 8 pictures.
  • Every failed reply carries _reportIssues: where and how to report a bridge bug, and what evidence to include (Bridge SDK).
  • A document that did not open is a failure on altium_open_document and every show_* verb too (0.2.25 covered only the library/board verbs).
  • altium_uninstall {dryRun:true} no longer stops the resident agent. altium_license refuses an unknown action by name instead of answering success.
  • Agent: rename_symbol / rename_footprint refuse an empty or already-taken name; set_layer_visibility refuses a layer id that is not in the board instead of dereferencing a nil layer object.
  • close catalog entry documents discardChanges, waitSec and its error codes.

0.2.33 (2026-09-21)

  • Every verb is documented for the AI: 122 of 122 have a catalog entry with summary, input, output, hint (the next call), related verbs, pitfalls and an example (98 had none). Each was written from its handler and its agent command. A unit test now fails the build if a verb is undocumented, documented twice, or names a related verb that does not exist. describe.facts lists the error codes added this week.
  • Two dialog verbs could answer questions nobody asked them. altium_dismiss_dialogs {hwnd, button} IGNORED hwnd and pressed the button on every dialog, save prompts included, although every hint in the bridge tells callers to pass {hwnd, button}; it now presses that one dialog. A sweep (no hwnd) skips save prompts and any dialog with more than one button. recover pressed OK on every dialog it found, and agent::call runs it automatically; it now touches only the lone-OK script error box.

0.2.32 (2026-09-21)

  • altium_agent_recover actually recovers, and says the truth. It sent EditScript:Stop and answered success without checking. That process only acts when a SCRIPT document is the active document: with a board in front it was a silent no-op, the script stayed on its breakpoint, altium_selftest_script_error reported halted_and_recovered for an engine that was never freed, and Altium then refused to close (AD 26.10.1; it had only ever worked where the script happened to be in front). Recover now brings each agent script in the workspace to the front (WorkspaceManager:OpenObject) and stops it there, warns if a hand-off started a second Altium, and VERIFIES by starting a fresh agent loop: engineFree:true, or errorCode: engine_still_parked with what to do.
  • altium_close frees a parked script engine before and during the close (Altium refuses to close "when a script is stopped on a breakpoint"), without starting a new agent loop.
  • altium_run_outjob / export_bom / export_gerber refuse a missing OutJob by name (outjob_not_found); the agent answered ok:false with no text.
  • Docs: PUBLISHING.md; every sub-skill starts with its Parent skill: line; README declares the altium keyword, describes the native exe (it still said "Python server") and points installs at altium_install; four blunt verbatim quotes in this changelog reworded.

0.2.31 (2026-09-21)

  • FIX altium_board_shape, which halted Altium's script engine on two builds (AD 26.10.1, Altium Develop). It drew four temporary tracks, ran the editor process PCB:PlaceBoardOutline, then read Board.BoardOutline.BoundingRectangle with no nil guard; on those builds the process left no outline, the read raised on a nil object and the script parked on a breakpoint with no error box. It now sets the outline the way altium_test_board always has (PointCount + Segments[] + Rebuild, which renders correctly on every build), with every access nil-guarded, and refuses bad_size / no_board_outline by name.

0.2.30 (2026-09-21)

From the first full battery on a third Altium build (Altium Designer 26.10.1, fresh install): 121 verbs, 106 pass, 6 fail.

  • The dialog scan saw none of the boxes that mattered. Confirm Save Locally (Save All | Save None | OK | Cancel), Confirm Not Saving and Zoom Value (OK | Cancel) matched no class allowlist, had more than one button and an uncatalogued title, so altium_check_dialogs answered "none" while they blocked altium_close for minutes. A titled window carrying two or more real dialog buttons is now a dialog whatever its class; the save prompts are catalogued as save_prompt and Zoom Value as input_prompt (safe action Cancel). The reaper still never presses a save prompt.
  • altium_close {discardChanges:true} now WORKS (the arg was accepted and ignored): it presses Save None and confirms, for callers whose modified documents are all their own. Without it a save prompt returns unsaved_changes with a screenshot naming the documents, instead of close_timeout.
  • Battery: never runs a demo OutJob (they open what they generate on the user's screen); show_project with no path is the bundled demo by design; read_schematic runs with its sheet shown.

0.2.29 (2026-09-20)

  • New verb altium_install: the install lane with its rules IN the verb. A from-scratch install on a fresh machine went badly because the AI driving it kept forgetting the rules in a 650-line skill: it asked the human to sign in on a page that was already signed in (it never looked), left Release / Assign / Use License / Refresh to the human although they are ordinary clicks, did not toast when it did need him, and launched the installer into an empty room so the UAC prompt timed out. Stages preflight, get_installer, launch, wait, license (or no stage: auto reads the machine). It verifies the installer's Authenticode signature (Valid, CN=Altium LLC) before anything runs, refuses launch without humanIsPresent:true, toasts the human itself and reports toast.displayed truthfully, and does the licensing clicks itself. Every reply carries _hint, _next, pitfalls and needsHuman, and needsHuman may only ever name a password/2FA, the UAC prompt, or clicks inside an elevated window.
  • detect.installVerb = altium_install and detect.hostAppOptionalVerbs declared, so ab forwards the install/readiness verbs when Altium is absent and bridge_readiness recommends this installer (two SDK audit misses).

0.2.28 (2026-09-20)

  • One thread per request. The bridge handled HTTP requests one at a time, inline in its accept loop, so ONE slow verb blocked every verb behind it, /health included: altium_signin's ~25 s UIA search, or an agent call waiting on a parked engine. ab dropped its connection at its own budget (an opaque "HTTP error"), the bridge kept working the abandoned request, and the queue only grew: the 30-verb cascades the verb battery produced, twice, from two different first causes. Each request now runs on its own thread. Agent-bound work still serialises at the agent's one-file inbox, which answers a NAMED agent_busy with busyWith and busyForSec instead of a dropped connection.

0.2.27 (2026-09-20)

  • The silent-halt fix in 0.2.26 modelled the wrong moment and did not fire. What really happens (heartbeat: requests: 1, then nothing): a command raises an exception the agent's own Try/Except handles, the command ANSWERS normally, and THEN Altium's debugger parks the script on a breakpoint, on Altium Develop with no error box. The caller of that command sees nothing; the NEXT verb finds a dead agent, starts a second script into a parked engine and blocks until ab's HTTP timeout, and so does every verb after it. agent::call now checks before bootstrapping: agent dead + Altium running + the agent script in front = parked, so it sends EditScript:Stop first, restarts the agent, runs the verb, and marks the reply enginePreviouslyParked:true (also logged in altium_dialog_log as engine_parked_cleared). The in-flight title trigger from 0.2.26 is removed: the title alone cannot tell a halt from a long command.

0.2.26 (2026-09-20)

Found by the new verb battery (dev-skills/altium-bridge-test), first full pass on Altium Designer Develop:

  • Silent script halts are caught. Altium Develop can stop the agent in its script debugger WITHOUT raising an error box (it did on board_shape): the box watcher from 0.2.21 saw nothing, the agent stayed dead, and ~30 verbs in a row queued behind it and timed out. Altium does give one tell: it pulls the agent's script document to the front. agent::call now treats "the main window flipped to AdomAgent_* while my command is in flight" as a halt after a 1.5 s grace for the box watcher: it photographs the editor, sends EditScript:Stop and returns agent_script_error with silentHalt:true.
  • Malformed numbers can no longer raise inside Altium. The agent called raw StrToFloat / StrToInt on caller values in 47 places; anything malformed (create_footprint with pads in the wrong shape) raised EConvertError, i.e. an Error box on the user's screen. All of them now go through AdomFloat / AdomInt, which never raise, and the dispatcher answers errorCode: bad_number naming the offending value. scripts/release.py refuses a build with a raw conversion outside those two helpers.
  • altium_import_part with nothing to import answered success:true; it now refuses with nothing_to_import.
  • New dev skill + runner: dev-skills/altium-bridge-test/run_verb_tests.py, every verb in the live catalog, coverage-enforced, a dialog check after every verb, scratch documents only, closes Altium when done (Author seats), and REFUSES to start unless the target machine has been idle (it raises Altium, and it did so over the user's typing on its first run).

0.2.25 (2026-09-20)

  • A document that did not open is now a failure, with the reason. altium_open_document (and the open every library/board verb does first) answered ok:true with shown:false buried in data when Altium silently refused the file; the verb then failed with a misleading "open the SchLib first" / "current document is not a schematic library". Now: errorCode: file_not_found, view_only_no_author_seat or document_not_opened, and 14 verbs that need the document stop there instead of running against the wrong one (run_drc's refocus and import_part's opens stay best-effort). Projects are exempt (they have no document view).
  • view_only_no_author_seat: measured on Altium Designer Develop. A second running Altium (another machine) held the workspace's only Author seat; Develop raised "Workspace Unavailable" at launch (acknowledged by the reaper), showed "There are no Author seats available ... view-only mode" behind the warning triangle, kept already-open documents working and refused every new one. The hint says so and gives the fix: close the other Altium cleanly, relaunch this one. No sign-in is involved.
  • The SOIC-8 / QFN-24 (9 heatsink vias) / BGA-36 suite now passes on BOTH Altium Designer 26.9.1 and Altium Designer Develop.

0.2.24 (2026-09-20)

  • altium_selftest_script_error knows both safe outcomes and reports which one it saw in outcome. Running 0.2.23 on a second Altium (Altium Designer Develop) showed the same deliberate error does NOT halt there: the engine raises a catchable exception, the agent's own handler answers ok:false in about 2 s and the loop never stops (contained_by_agent). AD 26.9.1 halts on a breakpoint and needs the bridge's recovery (halted_and_recovered). 0.2.23 called the first a failure. A timeout or a dialog left on screen is still the only FAIL. This is also why the 0.2.20 wedge never showed on the machine it was developed on.

0.2.23 (2026-09-20)

  • New verb altium_selftest_script_error: makes the agent raise a real DelphiScript runtime error on purpose and checks the safety net end to end (Error box photographed, halted script stopped with EditScript:Stop, no dialog left, netWorks: true). It is the regression test for the failure 0.2.21 fixed; run it after any change to agent.rs / dialogs.rs and once on each new Altium build.

0.2.22 (2026-09-20)

  • Script-error safety net, second variant. 0.2.21 caught the lone-OK Error box (an error in the agent's own code). Proving it with a deliberate bad altium_eval on AD 26.9.1 showed the OTHER shape: a script run from inside the agent raises an X2 box, "Undeclared identifier: ... at Line:N. Continue execution?", with OK + Cancel. The reaper rightly never touches a two-button box, so the agent sat blocked and the caller saw a timeout. agent::call now recognises that box while its own command is in flight, photographs it, answers OK (continue, so the agent loop survives) and returns errorCode: agent_script_error with errorShot, answered, agentAlive. It is logged in altium_dialog_log as script_error_continue.
  • The changelog line about removed internal files no longer names them.

0.2.21 (2026-09-20)

  • A script runtime error can no longer wedge Altium silently. Found by running the 0.2.20 suite on a second machine (AD 26.9.1 trial; SOIC-8, QFN-24 with 9 heatsink vias, BGA-36): a DelphiScript runtime error raises a lone-OK Error box, the standing reaper pressed OK within a second, and the script was left STOPPED ON A BREAKPOINT in Altium's script debugger. The agent loop was dead, every later verb timed out, Altium refused to close ("cannot close the current document when a script is stopped on a breakpoint"), and the error text was gone. Now: the reaper photographs an Error box before pressing it; agent::call sees it within 400 ms, sends EditScript:Stop itself (the altium_agent_recover steps) and returns errorCode: agent_script_error with errorShot (the PNG of Altium's message), autoRecovered and recoverSteps. The next verb restarts the agent.
  • FIX the error that exposed it: altium_symbol_prims {models:true} and altium_set_models read Comp.ImplementationCount / Comp.Implementation[i], which AD 26.9.1 does not expose to DelphiScript. Both now walk a component's models with the eImplementation child iterator.
  • scripts/release.py refuses to build an agent template that uses an identifier measured to halt the engine (ENGINE_HALTING: the list from the dev skill, as code).
  • Docs: every declared verb is in the README and a skill (21 were in neither); altium_plate_vias, altium_add_symbol_art, altium_symbol_prims, altium_set_models documented; new altium-a365 skill; install.sh installs all user skills (instances, licensing, uia, wiki-import, a365 were packaged but never copied).
  • Internal material removed from the public repo: machine names, an account display name and a private workspace hostname in PLAN.md / the plan skill / code comments and error strings; one internal memo deleted.
  • place_on_sch describe text matches the agent (sheet sized from the measured parts, not "A4 or B"). Repo-root bridge.json synced to the shipped src/bridge.json (it was a stale 0.1.41 leftover).

0.2.20 (2026-09-20)

  • New verb altium_plate_vias {libraryPath, footprintName?}: every drilled pad in a PcbLib (all footprints, or the one named) that sits on a layer other than Multi-Layer becomes a plated Multi-Layer pad (Pad.Layer := eMultiLayer, Pad.Plated := True). adom-lbr writes heatsink vias as pads with a hole and Altium loaded them on the Top Layer, so a thermal pad's vias arrived with their holes but were not through-holes (John, 2026-09-20: "did Altium support bringing in those heatsink vias?"). The reply carries holes (drilled pads seen), plated (pads changed) and fixed (Footprint:Pad;...). Modifies the open library in place: altium_save the PcbLib afterwards; altium_footprint_primitives on a placed part then reports layerId 74 for the vias. Pads already on Multi-Layer are left alone, so a second run changes nothing.
  • Version number: 0.2.19 was consumed by an interrupted publish, so this shipped as 0.2.20.

0.2.19 (2026-09-20)

  • altium_test_board silkscreen part name: its top now sits 2.5 mm below the part centre, clear of a 3 mm part's courtyard (0.2.18 overlapped it).

0.2.18 (2026-09-20)

  • altium_test_board writes each part's name on the Top Overlay under its footprint (35 mil stroke text, centred, its top 2.5 mm below the part centre so it clears the courtyard) so a human reading the board knows which chip is which (John, 2026-09-20: "place onto the silkscreen the name of the chip at that spot on the board"). Same rule as the KiCad and Fusion boards the ToF demo makes. Agent template only.

0.2.17 (2026-09-19)

  • altium_place_on_sch sizes the sheet FROM the parts: pass 1 measures every symbol's bounding box (pins included, +250 mil a side for pin numbers), the custom sheet is the row plus 500 mil between parts and 600 mil margins (never smaller than A4 landscape, 11500 x 7600), any slack is spread evenly, designators sit above and part numbers below each body. The SheetSizeX/Y read-back is gone: on ADDevelop it reports the 9500 x 7500 default after a custom size is set, which is what squeezed three parts on top of each other in 0.2.16. John's two reviews in one afternoon: "no real EE would smoosh symbols together, they'd space them out", then "a bit closer together so we get a better screenshot" - the numbers above are the ones he accepted.

0.2.16 (2026-09-19)

  • altium_place_on_sch: the designator sits centred ABOVE the body and the Comment (part number) centred BELOW it, whatever the library part did with them (+200 mil had put U1 on top of the chip outline inside a 3700 mil body); the description text frame sits INSIDE the drawing frame (700 mil below the top edge, 700 mil short of the right edge) instead of above the sheet; the sheet size is read back from the document (SheetSizeX/Y, UseCustomSheet := False before SheetStyle) so parts and text land inside whatever Altium actually gave us. Agent template only.
  • Doctrine, measured with John typing in Hydrogen on the same box: the capture verbs restore Altium INTO THE BACKGROUND, but the document steps (open_document, close_document, place_on_sch, install_library, agent_stop/agent_start) still raise the Altium window through Client.ShowDocument / script execution and steal his keyboard. Filed as an issue; until it is fixed, batch the document work and stop touching Altium the moment the proofs are in.
  • The "ganged-up view" (symbol + 3D chip in the Editor panel, footprint preview in the SCH Library panel): there is no server process that activates a docked panel TAB (Client:CustomizeResources Action=Show|ObjectKind=Panel|ID=TSchLibPanel only ensures the panel exists); a panel verb is on the wish list.

0.2.15 (2026-09-19)

  • altium_add_symbol_art replaces instead of stacking (clear, default true) and draws the outline as eLine segments (mode: lines, default), the primitive that BuildSymbolModes has always rendered with; ISch_Polyline objects rendered on VL53L5CX but stayed invisible on VL53L8CX with identical geometry inside the body (measured with altium_symbol_prims). symbol_prims gates its model walk behind models:true (the ImplementationCount / PartCount walk halted the engine on ADDevelop).

0.2.14 (2026-09-19)

  • A sheet a human would sign (John, reviewing the demo capture: symbols on top of the title block and outside the frame, empty title block, '*' comments). altium_place_on_sch now sizes the sheet for the part count (A4 up to two parts, B from three), places every part INSIDE the drawing frame centred on its body rectangle, sets each Comment to the part number, fills the title block from title / number / revision / drawnBy (document parameters behind the standard title block's special strings) and drops description as a text frame across the top. The reply carries sheetMil, titleBlock{} and _hints that name whatever the AI left empty and say how to write it.
  • The chip outline reaches Altium. New altium_add_symbol_art {libraryPath, symbolName, kicadSym} reads the polylines adom-symbol baked into the KiCad symbol (chip-thumbnailer's outline of the side-etched model), maps the KiCad body rectangle onto the SchLib component's rectangle and draws them as polylines, so the Altium symbol carries the same chip as the KiCad and Fusion ones. Appends; run once per part.
  • altium_test_board replies with _hints (list form, pcbPath, outline sizing, body check).

0.2.13 (2026-09-19)

  • Several parts on ONE document (John's ToF demo: three VL53Lx sensors on one schematic and one board). altium_test_board and altium_place_on_sch now accept a ; list in footprintName / symbolName: every part lands on the same new board (spread evenly along X inside the wMm x hMm outline) or the same new sheet (U1..Un, 5000 mil apart), and both save to pcbPath / schPath when given. Single-part calls are unchanged; assigns/stubs apply to the last part placed. Both verbs finally have describe entries. Agent template only: deploy the tmpl beside the exe and altium_agent_stop + _start; the 0.2.9 exe keeps working with it.
  • Measured on a second test machine (ADDevelop): altium_eval scripts that call PCBServer.PreProcess / PostProcess or even TStringList.SaveToFile halt before their first statement completes and leave an X2 error modal whose text is not readable (dialogs[].body empty); clear with altium_dismiss_dialogs {Cancel} + altium_agent_recover, twice. adom/altium-bridge#11 asks for readable eval errors and for append-to-existing placement.

0.2.12 (2026-09-08)

  • The one-click install no longer dies at Adom Bridge's 60 s budget (Colby, live on 2026-09-08: a 7-part Cloud Library export landed in Altium, SCH Library panel and all, while the page said "Could not install: 'altium_install_library_bundle' did not respond within ab's 60s budget"). ab enforces a per-verb budget it reads from bridge.json, and install_library_bundle was never declared there after 0.2.8 gave it a cold launch and 0.2.9 the open, panel-select and capture finish. Declared now: 330 s for the bundle install (the launch alone is 240), 150 s for install_library, and the verb's describe entry is marked long-running with altium_status as its status verb. The reply carries phasesMs (launch, licence, unpack, register, open, list, panel, capture, agentStop) and totalMs, so the next slow run names its step instead of leaving a guess.
  • The receipt survives a timeout. The bridge serves one verb at a time and finishes an install ab has already given up on; the receipt, proof frame included, is now kept in memory. altium_status carries a light lastInstall (with the installId the client sent), and the new altium_install_receipt returns the full reply. The wiki page waits through this path (git-wiki e1c66a1), so a timed-out install still ends in the success line and the proof shot.

0.2.11 — 2026-09-07

  • Fresh-workspace install fixed (adom/altium-bridge#10, found by the Hydrogen pre-production test: the dock's Altium Dashboard tile failed on every virgin box since 0.2.10). The 0.2.10 lint fix moved altium-bridge-dev out of the shipping tarball but install.sh still copied it, so cp failed and the whole install script was skipped, taking altium-dashboard with it. install.sh now installs only user skills; altium-bridge-publish moved to publish-skills/ in the same pass (publish skills are source-only, same doctrine); package.json files[] and page.json scrubbed of both stale paths. uninstall.sh still removes previously-installed copies. Runtime unchanged.

0.2.10 — 2026-09-02

  • Two-tier (insiders) publish adopted (adom/altium-bridge#9, John's ask; pattern from adom/pup-bridge which did it first). src/bridge.json declares insidersManifestUrl; a desktop on ab 2.0.99+ whose login can read the org's early-tier page follows the insiders manifest, everyone else follows the public one and never learns the field means anything. scripts/release.py --publish now ships to INSIDERS by default (zip on this page, manifest + release-log entry on the early-tier page, source stamps, skills pkg in lockstep per adom/wiki#170); the public manifest moves ONLY through --promote <ver> --attest "<the human's exact words>" --by "<name>", which refuses placeholders and records the words in the manifest. --rollback re-points the public tier at an archived build, also attested. No runtime changes beyond the manifest field.

0.2.9 — 2026-09-02

The dedicated freeze-fix release (adom/altium-bridge#6, #7, #8). Every mechanism here traces to the evidence gathered on a colleague's machine on 2026-09-02: three hard desktop freezes, all enabled by a resident agent loop the per-instance views could not see.

  • The workspace split-brain is closed. The agent REFUSES to bootstrap without a resolved instance (with a 12s resolve retry for just-launched windows) instead of silently landing in the legacy root where status and teardown were blind to it. altium_close now stops EVERY workspace's live loop (legacy included) before its WM_CLOSE; readiness/agent_status report agentResidentAnywhere + liveWorkspaces; altium_state (#8) answers from the union too, naming the workspace its logical read came from. install_library_bundle re-resolves the instance after its own auto-launch.
  • Close watchdog v2 detects natively and can end a wedged shutdown. 0.2.8's watchdog missed both freezes it watched because its detection ran through ab, whose enumeration hangs or returns empty exactly mid-freeze. v2 asks Win32 directly (non-messaging calls only; "cannot answer" is distinguished from "none"), drops stop files into every live workspace the instant the main window vanishes, and after 45s of "window gone, X2 alive, our loop was involved" it warns by toast and terminates the wedged process, recording the seat consequence. A ten-second blip instead of a bricked desktop.
  • The one-click install lands the user on their library (#7). After registering, the bundle opens the installed SchLib, selects the SCH Library panel (UIA SelectionItemPattern - a plain click does not switch panel tabs), captures the proof frame, and reports an honest shown: true|false plus real component counts (symbolCount; the old symbols/footprints file counts remain one release). The agent is STOPPED when the install lane finishes, so the user's later X-press finds an idle engine. The wrapper folder inside export zips is flattened (no more UI\UI\).
  • altium_close_document {path} (new verb): closes one open document through its document handle, never saving. The menu processes are broken for this on ADDevelop: RunProcess Client:CloseDocument no-ops and Client:CloseAllOpenDocuments unregisters documents while their views stay open (both measured).
  • Agent template hardening: Application.Terminated joins the loop's exit checks (Client.IsQuitting demonstrably never fired in time), idle auto-stop cut from 30 to 5 minutes (residency IS the freeze window), and the close_document command. Per the probe campaign on #6: no interception design is possible in this engine (timers die at script return, the VCL context is a sandbox, runtime event assignment is illegal in every spelling, TApplicationEvents is unknown to the dfm loader), and Client.StopServer rejects both plausible signatures, so server hygiene is deliberately NOT attempted.
  • Dialog catalog: "Error Reading Form" (dfm class-not-found; Cancel is the clean dismissal) and a body-text fallback that reads Text-role control names, so the script debugger's "Continue execution?" prompt (title "X2", previously empty body) classifies instead of evading the reaper.

0.2.8 — 2026-09-02

  • User-initiated-close watchdog, ported from Colby Knox's PR 74 (adom/altium-bridge#4) to the Rust runtime. A standing service polls every 2 s; for any per-instance workspace with a live heartbeat whose main window has vanished while an X2 process still lives, it drops the agent's stop file that instant, so the loop exits during shutdown instead of wedging under the engine teardown (the freeze that hard-locked a desktop). Cold starts never trigger it (a workspace must have been SEEN with a window). Each firing is logged in altium_dialog_log. Honest limit, as in the PR: a 2 s race, not a guarantee — the complete fix is the agent noticing shutdown itself.
  • altium_install_library_bundle auto-launches a closed Altium (Colby, #5): one-click parity with the KiCad/Fusion lanes; cold start runs with the measured taskbar bar, the agent bootstraps on the first call, and the reply carries launched with the cold-start time.

0.2.7 — 2026-09-02

  • The reaper pre-empts Altium's licence dialogs too (John, with two of them on his screen: "you should be handling this automatically for the user" / "you should pre-empt all of these dialog boxes btw"). Three gaps closed:
    • the scan no longer gates lone-OK dialogs on a window-class allowlist — any titled, visible, non-main window on an Altium pid whose only UIA button is OK is an acknowledgement, whatever its class;
    • the licence chooser (TThemedConfirmDialog, "You are not currently using an Altium Designer license", painted text) is recognised by its BUTTON SET (Choose a license | Continue): the reaper presses Choose a license and then Use License on that instance's License Management page via UIA — Continue would leave Altium in Viewer mode, which is never right for a bridge user;
    • License Renewal Failed (TFailedToRenewWarningForm) exposes NO buttons to UIA enumeration yet its OK clicks by name, so it is admitted by class and cleared by a TITLE rule; a title that matches the catalog now qualifies a window on its own.
  • Honest limit, stated in the reaper log's followup: a claim against a STRANDED seat (leased to a killed Altium) still fails with "maximum usage reached" — the bridge keeps that dialog off the screen, but only a sign-out/sign-in or the lease expiring frees the seat.

0.2.6 — 2026-09-02

  • The standing reaper was blind since 0.2.0 — fixed. The VaultExplorer startup warning was still reaching the user's screen. Root cause: ab's desktop_find_controls returns buttons under matches; the Rust port read controls, so every dialog scanned as buttons: [] and the lone-OK acknowledgement rule could never fire. Parser fixed with a regression test built from the exact reply ab gave for that warning; title-bar controls are dropped so a lone OK still reads as lone. Verified minutes later: two startup warnings reaped before anyone saw them.

0.2.5 — 2026-09-02

  • altium_foreground verifies instead of assuming: after the raise it reads the z-order and reports visiblyOnTop — a raise can "succeed" while another fullscreen window still covers Altium (John watched an entire show that never became visible because his fullscreen Claude window sat on top, and his own typing kept it there — which the foreground-once rule correctly refuses to fight). demo {live} surfaces the verdict as foregroundNote so the narrating AI can TELL the user the show is running behind their current window rather than silently losing to it.
  • The taskbar progress bar is visible during live-demo stages: pacing used the 210 s full-tour "demo" seed, so a ~12 s stage filled 0→7% — invisible. Live stages now pace against their own demo_live_stage phase (12 s seed), and every driving verb with a bar records its actual duration into the SAME key its estimate reads, so bars converge on this machine's measured truth instead of staying seeded forever.

0.2.4 — 2026-09-02

  • The live demo foregrounds Altium ONCE, at stop one — never again mid-show (John: "just foreground altium once. then don't try to foreground again for the rest of the demo just to make sure you strike a balance for the user"). The opening raise is an invitation; re-raising every stop would drag back a user who deliberately glanced at something else. Documents keep opening in Altium either way, so a watching user misses nothing.

0.2.3 — 2026-09-02

  • The punch_in preset stops flying the view into blank canvas. Altium zooms TOWARD THE CURSOR, so the 4-step zoom dive compounded drift toward wherever the user's mouse sat and scrolled the content off screen (John, watching take three: "you are like scrolling the content off the screen ... the screen going blank sometimes"). Per his fix: one gentle zoom-in/zoom-out breath, reframed with zoom_all — single-step drift is invisible.
  • The demo/live hints now teach the adom-tts cue lane (1.9.47's load/cue --confirm/cue-status, built from this thread's spec on adom/adom-tts#4): pre-cache the narration as markers, cue each stop in the background as the stage call starts, advance when both finish. Prefetched cues start instantly, which closes the last sync gap the say --play lane had.

0.2.2 — 2026-09-02

  • The live show is code now, not a hand-rolled AI script (review feedback: everything the demo needs should be a verb, and the sync jitter came from driving the show step by step from the AI instead of from code). Driven from a container, every camera step paid the full CLI→ab→bridge→agent round trip (~1.4 s, jittery); in-bridge it is one agent file-drop (~40 ms), so choreography holds its timing.
    • altium_animate_view — camera/zoom choreography on the current document from NATIVE Altium camera commands (SetCamera/ResetCamera/FlipBoard/Zoom; Altium animates the transitions itself — no synthetic mouse). Steps (orbit_left/right/up/down, isometric, flip, zoom_in/out, pans, rolls…) or presets (punch_in, showcase3d, orbit, flip, finale), each with a pauseMs.
    • altium_foreground — the sanctioned exception to the never-foreground rule, for a human who asked to WATCH. Refuses without a human-readable reason (>=10 chars), mirroring ab's caption rule.
    • altium_demo {live:true} — the narrated, animated, foreground tour: per stop it paints the ab caption, opens the document, holds Altium in front, runs the choreography in-bridge, and returns narrate + nextNarrate so the caller's TTS speaks over exactly the motion described (fire nextNarrate detached, then make nextCall). When Altium is cold it narrates the launch too: caption + a spoken line + a background launch, status: "launching" until up.
  • Fixed the zoom that never happened: schematic zoom-in is Sch:Zoom Action=ZoomIn — Action=In is silently IGNORED by Altium (John watched a whole stop where "pushing in" never moved). The rcs catalog carried the truth all along; the step map now encodes it.

0.2.1 — 2026-09-02

  • A finished verb can no longer leave a stale partial progress bar on Altium's taskbar icon. John spotted it minutes after the 0.2.0 swap. Root cause: every progress paint is an ab round trip, and the clear was painted from the dispatch thread — so a tick still in flight from the ticker thread could land AFTER the clear and park a "47%" frame on the icon forever (a race the Python bridge carried latently; the Rust timing exposed it). The terminal frames (error flash, clear, flash:stop) are now painted by the ticker thread itself, where paints are sequential, so the clear is guaranteed to be last; the ticker also sleeps in 100 ms slices so the clear lands promptly.

0.2.0 — 2026-09-02

  • The bridge is Rust, end to end. John: "let's finish getting everything into the rust code. i don't like that we're half rust half python now." All 112 verbs now dispatch from bridge-rs (altium-bridge.exe, spawn.kind: "exe" per ab's SDK); the Python runtime no longer ships. What crossed over, at behavioural parity with 0.1.41:
    • the instance layer (instance:<pid|key|word>, ambiguous_instance refusal, per-instance agent workspaces agent/<key>, instance-aware licence gate incl. the Develop absent-tail rule);
    • background proof capture (restore-minimized-into-background → capture → hand the foreground back; sub-400px slivers refused as proof_unavailable);
    • the standing dialog reaper (catalog safe-actions only, lone-OK acknowledgement rule, never a save prompt, dialog_log ring) and the in-flight -R modal watcher;
    • taskbar signals (driving badge, measured-estimate progress bar, 5-minute idle auto-clear) and the measured-progress store — same on-disk paths/JSON as the Python bridge, so each machine's history survives the engine swap;
    • the seat rules (force-close refuses without acceptSeatLoss, seat hints on every licence-shaped failure, UIA-first licence page driving via UseLicenseButton);
    • all surfaces, library authoring, import_part (zero-model attach still fails loudly), install_library_bundle (hardened unzip in Rust), outputs (compile/ERC, OutJobs, STEP via the extension, the DRC modal dance), review_board (all 14 checks as pure functions, now fixture-tested), cross_check, the rcs command catalog, and the Altium 365 OAuth/GraphQL lane (PKCE per RFC 7636, verified against its test vector).
  • The verb lint now reads the RUST dispatch (verbs.rs KNOWN) against bridge.json, and the release also refuses on version drift across bridge.json / BRIDGE_VERSION / package.json / main.rs. cargo test (38 tests, incl. the agent-generation digest interop test that keeps Rust and the DelphiScript template in lockstep) runs before every build.
  • Two Rust-side views that existed but were never declared — altium_board_summary, altium_rules_by_kind — are now in bridge.json (declared-vs-dispatchable is exactly what the lint exists for).
  • Still intentionally not Rust: the DelphiScript agent template (it runs INSIDE Altium; that lane is Pascal by nature) and the container-side converters (tools/kicad2altium.py, tools/symbol_modes.py), which never ship in the bridge zip.

0.1.41 — 2026-09-01

  • The tab favicon actually reaches the tab now. 0.1.40 shipped the correct line-mark favicon.svg, but the dashboard's icon route aliased EVERY icon path — /favicon.svg included — to dock-altium.svg, which is a 60 KB SVG wrapping an embedded PNG raster. Hydrogen's webview reads the page's favicon after load (hydrogen-tab-icons) and renders nothing for that blob, which is precisely the icon-less tab John reported twice. The favicon route now serves ui/favicon.svg (the monochrome #e6edf3 line mark, same file as the header icon); /dock-altium.svg keeps serving the dock card art.
  • install.sh now installs favicon.svg. Its asset list was explicit, so 0.1.40's new file never landed in ~/.local/share/adom/altium-bridge/ — the pkg carried it and the installer dropped it.
  • Verified end to end this time: the live server returns the 340-byte mark for /favicon.svg, and a Hydrogen workspace capture shows the restored header (mark, subtitle, pill chip with LED, ghost buttons) live in the dock tab.

0.1.40 — 2026-09-01

  • The dashboard header is whole again, and the dock tab has a favicon. John, from one glance at the dock: "why don't i see a favicon ... your header looks wonky ... almost like you're missing a css." Both right. index.html linked favicon.svg but the file did not exist (the route 404s honestly since the 2026-08-25 route-table sweep, so the tab showed the generic mark), and an earlier CSS deletion had taken the header's visual rules — .iconbtn, .vsep, the target-chip pill, the brand font — while the markup and a .iconbtn:focus-visible rule survived, so the buttons rendered as UA-default white boxes on the dark bar.
  • Restored per dash-header's numbers: 28x28 ghost buttons with 18px glyphs (muted at rest, teal on hover), mono pill target chip with the LED inside, 1px vsep, name in Familjen Grotesk 600 15px var(--text) — NOT the accent, per the 2026-08-24 ruling, deliberately unlike fusion's current teal name.
  • favicon.svg is the SAME line-drawing mark as the header SVG, as the icons skill requires — one mark, tab and header.

0.1.39 — 2026-09-01

  • 0.1.38's idle-watcher never actually started. The boot-site call used taskbar unimported (dispatch imports it lazily inside the handler), so boot logged name 'taskbar' is not defined — and logged it as a DIALOG REAPER failure, because the watcher call sat inside the reaper's try-block. Fixed the import, and each boot service now has its own try so a failure names the thing that failed. Verified by the boot log this time, not by assumption.

0.1.38 — 2026-09-01

  • The taskbar overlay badge auto-clears after 5 minutes without a driving verb. John, seeing the Adom mark still parked on Altium's taskbar icon: "do you auto-remove that overlay after 5 minutes of verb inactivity?" The answer was no — the badge was set on the first driving verb and never cleared until the bridge died, while this module's own header PROMISED "cleared when the bridge goes idle". A permanent overlay stops meaning "an AI is driving right now" and starts meaning nothing. An idle watcher (started at boot beside the dialog reaper) now clears it at 5 idle minutes; the next driving verb re-badges instantly.
  • For the record, what already worked: every driving verb with a measured estimate ≥3 s runs a REAL taskbar progress bar on Altium's button (percent paced by history, marquee when unmeasured, red flash + clear on failure) and stops it when the verb returns — progress rides the taskbar icon, never toasts, per the house rule.

0.1.37 — 2026-09-01

  • Altium's dialog text is unreadable — so the reaper stops relying on it. Measured: the VaultExplorer warning's entire UIA subtree is Window "Warning" -> panes -> Button "OK". The message is PAINTED pixels, absent from WM_GETTEXT and from the accessibility tree, so no amount of text classification will ever see it.
  • The rule that does work: a dialog whose ONLY button is OK carries no decision. Dismissing it can acknowledge, never choose. Everything that asks something of the user has two or more buttons — a save prompt (Save / Don't Save / Cancel), a licence chooser (Choose a license / Continue), a confirm (Yes / No). The standing reaper now clears lone-OK dialogs even when their text is unreadable, logging them as single_ok_acknowledgement.
  • Multi-button dialogs are still never improvised on: if the catalog's safe action is not literally one of the dialog's buttons, the reaper leaves it alone.
  • This is what finally makes Could not start Vault Explorer — and any future painted, single-OK Altium warning — invisible to the user.

0.1.36 — 2026-09-01

  • The bridge can finally READ Altium's dialog messages. Altium PAINTS its dialog text instead of using Static controls, so WM_GETTEXT returned nothing but the BUTTON labels — Could not start Vault Explorer… came back as a body of just "OK". Every such dialog therefore classified as unknown, which is why the standing reaper (correctly, per its own rules) refused to touch the exact warning it was built for.
  • _body_of() now strips the button labels and, when what is left is too short to be a message, asks UIA (desktop_ui_text) for the painted text. Classification, the reaper, check_dialogs and every _hint that quotes a dialog all get the real message.
  • This was the missing half of 0.1.35: the reaper was correct, its input was blind.

0.1.35 — 2026-09-01

  • A standing dialog reaper, running from bridge start. John: "and always pre-empt these from ever showing up." Clearing a dialog after a verb notices it is not pre-emption — the user has already seen it. The reaper polls every 1.5 s and presses the catalog's SAFE action the instant a KNOWN nuisance appears, whether or not any verb is in flight. That is the only thing that can catch Altium's STARTUP warnings (Could not start Vault Explorer, File type not recognised), which appear with nothing running.
  • Hard rules, so it can never answer for the user: only dialogs whose catalog entry declares a safe action; never unknown; never a save prompt (those name documents that may belong to another AI thread and are surfaced for a human decision). Every press is recorded and readable via the new altium_dialog_log.
  • Catalog additions with safe actions: Could not start Vault Explorer (Altium's own extension failing to load — NOT the bridge: the extension is correctly registered, its payload is complete at 115 files including VaultExplorer.dll, and Altium's private .NET 8.0.15 runtime loads fine), File type not recognised (at startup it is Altium restoring a document it can no longer open).
  • 110 verbs.

0.1.34 — 2026-09-01

  • The force-close guard now fires on INTENT, not on a blocked dialog. 0.1.33 put it inside the blocked-by-dialog branch, so altium_close {force:true} sailed straight through whenever nothing happened to block — exactly the path that strands the seat. It is now checked at the top of the verb: force:true on a licensed instance is refused with would_strand_licence_seat unless acceptSeatLoss:true.
  • altium_instances applies the Develop licence rule too. 0.1.33 fixed only licence_state(), so the instance list still reported Altium Designer Develop as unlicensed while its agent was running and importing.

0.1.33 — 2026-09-01

  • Three new skills, because AI reads verb hints and skills, not tribal memory (John: "ai hints are way more valuable to help the ai know how to navigate the stuff you didn't know how to"):
    • altium-licensing — one On-Demand seat serves EVERY Altium app (Designer and Designer Develop share it); killing an instance strands the seat and the licence page then reports it FREE while refusing every claim; the only recovery is a polite altium_close then use_license. Also: Develop writes NO licence tail in its title when it HOLDS the seat.
    • altium-instances — addressing one Altium of several, why ambiguous_instance is a feature, the per-instance agent workspace, and the three places single-instance assumptions hid.
    • altium-wiki-import — wiki part page or Cloud Library → Altium, the Cloud Library's Altium gap (phase 3) and the pipeline that closes it, plus the background proof captures.
  • AI hints where the knowledge bites, not only in the skills: altium_instances and every not_licensed refusal now carry _licenceHint / _seatHint explaining the one-seat rule, the stranded-seat signature (Seats 0/1 while claims fail) and the exact fix.
  • altium_close {force:true} refuses unless acceptSeatLoss: true — a forced kill strands the seat, and this used to be one keystroke away. The blocked-by-dialog reply now also warns that an Unsaved Changes prompt NAMES documents that may belong to another AI thread.
  • Dialog catalog gained License maximum usage reached, License Renewal Failed (re-fires on a timer; clearing it is not the fix), Cannot focus a disabled or invisible window (Altium's crash reporter — its only button SENDS data) and a sharper Unsaved Changes entry.
  • Title parser: an absent licence tail on Altium Designer Develop now means LICENSED, not unknown. altium_instances reported ADD unlicensed for an entire test while its agent imported five parts.

0.1.32 — 2026-09-01

  • Instance-awareness now reaches the licence gate. main_window() returned "the first Altium main window" regardless of the selected instance, and licence_state() → _need_licensed() are built on it — so with two Altiums running, every verb targeting one instance was judged against the OTHER instance's licence. Measured live: altium_license_status {instance:"develop"} correctly reported Trial, while altium_agent_start {instance:"develop"} was refused not_licensed because Designer held no seat.
  • Verified with a real second instance (Altium Designer Develop): the layer distinguishes AD26/Designer from ADDevelop/Develop, gives each its own agent workspace, and REFUSES a verb with ambiguous_instance when no selector is given — rather than acting inside the other AI thread's Altium.

0.1.31 — 2026-09-01

  • Systemic fix: ad_callback now unwraps ab's reply envelope. ab's direct API answers {success, status, error, output} with the verb's REAL result JSON-encoded as a STRING in output. The Python bridge never unwrapped it, so EVERY field read back from an ab callback — hwnd, windows, processes, controls — was silently None. Calls that only ACTED (click, kill) worked, which is why this hid for so long; anything that tried to READ a result quietly saw nothing.
  • Found because 0.1.30's background-capture helper decided a minimized window was not minimized: it was reading rect out of an envelope it could not see into. The Rust bridge has always unwrapped (ab.rs::unwrap_output) — this brings Python in line.

0.1.30 — 2026-09-01

  • Proof captures work with Altium minimized, without ever moving the window. Windows does not render a minimized window, so a capture of one returned a ~237x39 title-bar sliver — which the bridge handed back as "proof" of a successful import, and which the wiki front-end would have displayed as evidence. Now every capture first makes the window CAPTURABLE: restore it if minimized, push it to the BOTTOM of the z-order, and hand the user's foreground straight back. Measured end to end at ~0.6 s; the capture then reads the occluded window at full size.
  • The rejected alternative, recorded so nobody rebuilds it: parking the window off-screen. Rejected in review on 2026-09-01: a user who brings Altium forward by hand would not find it. Never move a window the user might reach for.
  • A capture narrower than 400 px is refused, not returned. _shot_of_main reports proofUnavailable with the reason, and the surface verbs (show_symbol / show_footprint / show_3d_chip / …) return success: false, errorCode: "proof_unavailable" rather than a confident nothing. The frame also carries prepared — what the bridge had to do to the window — so a caller can say honestly whether the user's screen was touched.

0.1.29 — 2026-09-01

  • import_part no longer reports a silent half-import. The add_models step recorded a bare boolean, so attaching ZERO footprint models looked identical to attaching one — and a symbol with no model places on a schematic with no footprint. It now surfaces the agent's own counts and FAILS with failedAt: "add_models" when the count is zero.
  • It also sanitises the model spec the way add_models always did: the agent splits models on ; and fields on |, so an unescaped separator inside a DESCRIPTION silently truncated the spec. import_part passed the description raw.
  • Known defect, recorded not fixed: add_models is not idempotent — each call appends another implementation, so calling it twice leaves a symbol with duplicate footprint models. Removing a model needs an implementation-read API this agent has not proven yet, so the safe repair today is remove_symbol + re-import.
  • Verified end to end against a real wiki component (adom/stm32f303cbt6, published by ray): kicad_sym → 48-pin symbol with 10 display modes, kicad_mod → LQFP-48 land pattern with courtyard/fab/pin-1 marker, STEP → 3D body, all three vendor part numbers attached, zero dialogs, 7.75 s, and the user's foreground window never changed.

0.1.28 — 2026-09-01

  • Four design-rule hygiene checks, all from the rules the board read already carries:
    • disabled_design_rules — a rule that exists but is switched OFF. Worse than a missing rule: the board LOOKS ruled, DRC passes, and the constraint was never applied. Raised to error for Clearance / Width / HoleSize / ShortCircuit.
    • missing_critical_rules (error) — no ENABLED rule of a kind a fabricator relies on.
    • duplicate_rule_priority (info) — two enabled rules of one kind sharing a priority. Altium resolves the tie internally, so which one wins is not readable from the board; usually a copy-paste never re-prioritised.
    • components_without_footprint (warning) — a placed part with an empty footprint pattern name, so it cannot be matched back to a library or checked for updates.
  • Rule kinds are named from the ordinal table read off a live default rule set; an unknown ordinal renders as kind <n> rather than being invented.
  • 14 checks.

0.1.27 — 2026-09-01

  • review_board switches documents and returns PARTIAL results. Altium holds one current document, so a board read and a sheet read cannot both be "now" — with a schematic focused, the previous version failed the whole review because read_board had nothing current. It now takes optional board and sheet paths and activates each in turn (like cross_check), and when one document cannot be read it still runs the checks that CAN run.
  • skipped names every check that did not run and why — "no board read", "no schematic read — pass includeSchematic:true". A review that silently drops half its checks reads exactly like a clean board, which is the failure mode worth engineering against.

0.1.26 — 2026-09-01

  • The Adom house rule is now a check. vendor_part_numbers (warning) flags any purchased component missing Mouser / DigiKey / LCSC part numbers — Mouser named first because it is the preferred vendor, so if only one gets filled in, that is the one. Test points, fiducials and mounting holes are exempt: they are not purchased parts, and flagging them would train people to ignore the check.
  • missing_datasheets (info) and schematic_placeholder_values (error) — the latter catches default Comments at the SOURCE, where the BOM is actually generated from, rather than only on placed parts.
  • The schematic read now carries component parameters (params: "1"), using the same MkSet(eParameter) iterator the parameter WRITER has always used — so three checks arrived with no new Altium API surface. Capped at 40 parameters per component.
  • The three schematic checks return nothing unless includeSchematic: true, since without a sheet read they have nothing to judge.
  • 10 checks.

0.1.25 — 2026-09-01

  • checks:[...] now filters on the ID you actually see. The selector was derived from the Python function name (_chk_components_outside_board) while findings report components_outside_board_outline, so filtering by the visible name silently ran ZERO checks and returned a clean review. Each check now declares its own ID, the reply lists availableChecks, and an unrecognised name comes back as unknownChecks instead of being ignored.
  • New verb altium_read_board — the raw read (components, nets, layers, copper, rules, occupancy, outline) in one round trip. It existed only in the Rust bridge, which meant nobody could see what a check saw; a review's silence had to be taken on trust. Now it can be inspected directly.

0.1.24 — 2026-09-01

  • read_board gains an outline section — the board extent, read through the SAME guarded pattern the library reader already uses, because a library's internal board has a nil BoardOutline and touching BoundingRectangle on it halts the engine (measured 2026-08-30). It reports which source it used: boardOutline (the real outline's bounding box) or boardBounds (everything on the board, including strays outside it).
  • Two review checks unlocked by it, both named for what they actually measure:
    • components_outside_board_outline (error) — a part outside the board's bounding box.
    • components_near_board_edge (warning) — a component ORIGIN within a margin of the edge (default 20 mil). Not called pads_near_board_edge: per-pad geometry is not in the board read, and the catalog's name would claim a precision this does not have.
  • Both go silent when the extent came from boardBounds. That fallback includes stray primitives outside the real outline, so using it to declare a part "off-board" would produce confident false errors. A weaker source yields no finding rather than a wrong one.
  • notImplemented now names the bounding-box limitation explicitly: on an L-shaped or slotted board, a part inside the box but outside the real outline is NOT caught.

0.1.23 — 2026-09-01

  • altium_review_board: design review over ONE board read. Phase 2 of the plan, from the lint catalog. Five checks, all pure functions over data the bridge already reads — so adding them cost no new Altium API surface, which is the whole point: a wrong identifier in the agent halts the script engine behind a modal on the user's screen.
    • placeholder_values (error) — a component still carrying Altium's default Comment. It reaches the BOM as "Comment".
    • duplicate_designators (error) — the same designator twice on a sheet or board.
    • single_pin_nets (warning) — a net with one pad connects to nothing; usually a typo'd label.
    • unused_mechanical_layers (info) — an enabled mechanical layer carrying nothing. It travels into fab outputs and is invisible in the editor precisely because it is empty.
    • off_grid_components (info) — a placed part off the grid (configurable; a metric board legitimately fails a 5 mil test).
  • A clean result is not claimed to be a complete one. The reply carries notImplemented naming the checks this review cannot see yet and why — board outline, ratsnest, silkscreen geometry, via topology, decoupling. Silence is not a pass.
  • Findings name the exact designator, net or layer and say what is wrong; never "verify X". Sorted error → warning → info, with counts.

0.1.22 — 2026-09-01

  • altium_cross_check reads the two documents in TURN, because Altium cannot hold both current. PCBServer.GetCurrentPCBBoard follows the FOCUSED document, so focusing the schematic clears the current board — the first cut of this verb read the sheet, then failed on the board every time. It now activates the board, reads it, activates the sheet, reads it, and reports the document the user had in front of them. Takes board and sheet paths, or uses what is already open.
  • The two caveats still travel with the answer: one sheet only (a hierarchical parent holds sheet symbols, not parts), and a folder mismatch between sheet and board is reported as sameProject: false with a warning, because comparing two unrelated documents makes every difference meaningless.

0.1.21 — 2026-09-01

  • "File type not recognised." pre-empted at the cause. The agent opened every non-project file with ObjectKind='Document', which Altium rejects with a modal on the user's screen even for a valid .PcbDoc. It now maps the extension to the kind Altium actually uses (PCB, SCH, PCBLIB, SCHLIB, OUTPUTJOB, PCB3DLIB, TEXT, Project) and opens through Client.OpenDocument — the sibling of ShowDocument, which this unit already relied on — falling back to the process lane only if that raises.
  • The bridge refuses unsupported types before Altium can complain. An extension outside the openable set returns unsupported_document with the supported list, instead of handing Altium something it will answer with a dialog.
  • A slow open is no longer reported as a failure. ab gives a verb 60 s; a cold document open can exceed it. open_document and activate_document now wait just under the budget and return status: "in_progress" with a poll hint, rather than timing out while Altium is still working.

0.1.20 — 2026-09-01

  • altium_read_schematic and altium_cross_check in the shipped runtime (they existed only in the Rust bridge). cross_check compares the current sheet against the current board — what is drawn but not placed, and placed but not drawn — which neither read answers alone. Two caveats travel WITH the answer because both make it wrong to act on: the schematic side is ONE sheet (a hierarchical parent holds sheet symbols, not parts), and Altium's SchServer and PCBServer each hold their own "current document", so the two can belong to different projects — detected by comparing folders and reported as sameProject: false with a loud warning.
  • Manifest drift fixed, in both directions. bridge.json declared three verbs with no handler (close_document, export_pdf, screenshot_all) — ab routed them and the bridge answered "Unknown command" — and omitted three that work (ensure_licensed, license_status, setup_trial), leaving them unreachable through ab. The phantoms are gone (they never worked; implementing them is separate work) and the three real ones are now routable.
  • The release refuses to build on drift. scripts/release.py lints bridge.json against what server.py can actually dispatch and fails with both lists rather than shipping a manifest that lies. 106 verbs declared and dispatchable.

0.1.19 — 2026-09-01

  • Pre-emption, properly. Two changes so a script modal cannot reach the user:
    1. A script lane now refuses when ANY resident loop is alive, in ANY workspace — not just the current instance's. That gap is exactly how "Another script executing now" got on screen after per-instance workspaces landed: the pre-upgrade loop was still resident under the legacy path, is_alive() looked only at agent/<key>, saw nothing, and fired a bootstrap into an engine that was already running a script. The refusal names the workspace and heartbeat age so the fix is obvious.
    2. A watcher runs FOR THE DURATION of every -R lane and presses OK the instant one of our own modals appears, so it exists for a few hundred milliseconds instead of sitting on screen until someone goes looking. Cleared modals come back as modalsPreempted rather than being hidden. The watcher only ever touches modals this bridge CAUSES (script-busy, another-instance, scripting-system); a save prompt or a licence question is the user's and is never pressed.
  • Detection was always the weaker half: the engine is single-threaded, so the modal it raises is also what freezes it — which is precisely why watching during the operation works.

0.1.18 — 2026-09-01

  • Instances. More than one Altium can run at once — John runs Altium Designer Develop beside Altium Designer so two AI threads can drive both — and the bridge assumed one in every place it mattered. New verb altium_instances lists each running Altium with the pid/key to address it; every other verb takes an optional instance (a pid, a key like ADDevelop, or a word from the edition/title). One Altium running: omit it, nothing changes. Several: verbs REFUSE with ambiguous_instance rather than guess, because guessing means acting inside the other thread's Altium.
  • Each instance gets its own agent workspace (bridges-cache/altium/agent/<key>), keyed by install folder so it survives a restart. Two resident loops sharing one request.txt is the failure that does not announce itself: whichever loop notices first eats the other's request and answers it from the wrong board.
  • The -R lane spawns the selected instance's own exe, not a hardcoded AD26 path — Designer and Designer Develop live in different install folders.
  • Dialog scanning is scoped to the selected instance, so one thread can no longer see, or dismiss_dialogs clear, the other thread's modal.
  • The stray-helper reaper can no longer kill a real Altium. It now requires -R…ScriptingSystem in the process's own command line (ab reports it). The old rule — "any X2 that is not the main window's process" — killed John's Develop instance on 2026-09-01 while it was hosting another thread's agent.
  • 97 verbs.

0.1.17 — 2026-09-01

  • New verb altium_layers_in_use: how many layers the board ACTUALLY has. Until now the only layer answer was "48", which is every copper layer Altium CAN address, not a property of the board. Altium's in-stack flag has no identifier proven on AD26 and a wrong guess halts the engine behind a modal, so the agent's new layer_occupancy counts primitives by layer in ONE pass (using only identifiers already shipped in this unit) and the bridge filters that tally to the copper layers. Verified live: LAN7800_test reports 1 copper layer in use (Top Layer), 7 layers carrying primitives, against 48 addressable.
  • Read the result as a FLOOR: an inner plane carrying no primitives is invisible to a primitive count. The reply says so, and copperLayersAddressable is labelled as a capability rather than a board property.
  • read_board gains the occupancy section (default on).
  • The first cut of this view forgot to filter to copper and cheerfully reported a MECHANICAL layer (296 primitives) as the board's busiest layer. Now covered by a unit test against that exact shape.
  • 96 verbs.

Unreleased — bridge-rs only (no change to the shipped Python runtime)

  • check_dialogs no longer sees other applications' windows. The first cut matched window TITLES containing "Error"/"Warning" and picked up four "RemoteApp security warning" windows on John's desktop that had nothing to do with Altium — which dismiss_dialogs would then have clicked OK on. Pressing buttons in someone else's application is exactly what this bridge must never do. Now scoped by PID: process_list for Altium's pids, desktop_enum_windows {pids} for only those windows, then class + "not the main window". Also 300× faster (one enumeration instead of six title searches): 1 ms.
  • Note for the Python bridge: it was never affected — handlers/dialogs.py has always attributed dialogs by pid.

0.1.16 — 2026-09-01

  • Renamed dump_board → read_board (agent command and Rust verb), before anything depended on it. John, on the 0.1.15 naming: "that's not a great verb name ... we should probably pick a better verb thats more like a read or write*"*. He is right — "dump" names the implementation rather than the caller's intent, and it has no counterpart, so it breaks the symmetry the moment writes arrive. The convention is now recorded in the dev skill and the Rust README: read_* to get data out, write_* / apply_* to put it back. Rust module dump.rs → board.rs, BoardDump → BoardData, fixture read_board.json.
  • For the record, since it came up: the name was mine, not inherited — no verb names were copied from any third-party Altium MCP server.

0.1.15 — 2026-09-01

  • Agent gains dump_board: components + nets + layers + copper + rules in ONE round trip, composed from the existing readers so it adds no new Altium API surface (and therefore cannot introduce an engine-halting identifier). This is the primitive the Rust bridge shapes its views from; the Python verbs are unchanged.
  • Rust bridge (bridge-rs/): cold bootstrap through the -R lane proven live — 1.59 s, zero dialogs, which was the last unverified path. Dump-derived views land with it (board_summary 232 ms, rules_by_kind 152 ms), plus fixtures/dump_board.json — a recorded live dump that lets cargo test exercise all the shaping logic with no Altium, no Windows and no agent. 8 tests; they already caught a dispatch bug and one wrong assumption about the Fanout rule group.
  • rule_kind_name(): Altium emits TRuleKind as a bare ordinal; 31 ordinals are now mapped to names read off a live default rule set. Unknown ordinals render as kind <n> rather than being invented, and a test fails loudly if a board carries one we do not know.

0.1.14 — 2026-09-01

  • altium_activate_document re-implemented over the AGENT instead of UIA. 0.1.13 shipped it as a UIA document-bar click; measured on a live box that call takes >100 s (ab's budget is 60 s) because Altium's main-window UIA tree is huge. The agent runs IN-PROCESS: ~4 s, no cursor, no OS foreground steal, and Altium activates an already-open document rather than reloading it. Verified: title switched to the target .PcbDoc and board verbs answered in 0.2 s.
  • altium-uia skill corrected accordingly — the rule has two halves: UIA for dialogs and WebView pages (small trees, fast), the resident agent for anything inside the application. desktop_click only when neither can reach it.

0.1.13 — 2026-09-01

  • UIA first, cursor last. John, watching the bridge drive his box: "you keep turning on the cursor, but you could maybe use UIA to do those clicks". He was right — altium_license had been clicking hardcoded pixel coordinates since 0.1.x. Altium DOES expose a UIA tree, including the WebView2 License Management page (automationId: UseLicenseButton, invokable). The licence lane now invokes programmatically (no foreground, no cursor movement) and falls back to the image-space click only on a genuine UIA miss; the reply says which lane ran (lane, windowEtiquette).
  • New verb altium_activate_document {name} — every open document is an invokable button in Altium's document bar (GUIBinderView.TGUIDocBarButton, accessible name = the tab caption). Making a .PcbDoc current no longer needs open_document (which raises Altium's window) or a pixel click on the tab. 95 verbs.
  • New DEVELOPER skill altium-uia: the measured catalog of what AD26.9.1 exposes to UI Automation (dialogs, main menus, title bar, document bar, licence page) plus what it does NOT (the canvas — that is the agent's scripting API), the two costs (UIA searches over Altium's tree are slow; nothing reaches an app while a higher-integrity window such as a screensaver owns the foreground), and the honest list of what still legitimately foregrounds.
  • Diagnosed the "Could not start Vault Explorer" warning users see on every launch: the extension is correctly registered (ExtensionsRegistry.xml) with a complete payload, and Altium's private .NET 8.0.15 runtime loads fine, so it is neither a broken registry nor a runtime mismatch. Investigation continues.

0.1.12 — 2026-08-31

  • 14× faster agent round trip: 450–600 ms → 31–47 ms. Root cause: Break inside a Try..Except in this script engine is implemented as an exception, so the Except arm swallowed it — every file write ran all 20 retry iterations (Sleep(10) each, ~220–310 ms) even though the FIRST write had already succeeded. Both write primitives now use a Done flag with a While guard instead. Verified across the whole template: no other Break-inside-Try site exists.
  • hb.txt keeps tmp+rename (readers hold it open, and a direct rewrite raced them into EFCreateError); response_<id>.json writes direct — one file op instead of save+delete+rename.
  • Responses carry agentPhases — {readMs, dispatchMs, detectTick, sleepMs, hbSaveMs, hbSaveTries, hbSaveExcept, directMs, directTries, directExcept}. *Tries: 1, *Except: 0 is the healthy shape; anything higher means the box is contending for the file and the retry loop is earning its keep.
  • New agent-side io_bench command (not a bridge verb): times SaveToFile new/overwrite/delete against the workspace, %TEMP% and Documents, plus rename/Sleep(1)/loop cost. This is what localized the stall — keep it for the next latency regression.
  • Measured after the fix (LAN7800_test.PcbDoc, live): list_designators 44 ms, list_nets 57 ms, read_rules 68 ms (35 rules), layers 47 ms (28), copper_layers 67 ms (48), component_data 87 ms — agent round trip, dialogs 0.

0.1.11 — 2026-08-31

  • Phase 1 of the parity plan: 11 read-only board/schematic introspection verbs — list_designators, component_data, component_pins, list_nets, net_connections, layers, copper_layers, schematic_data (pins + part IDs), read_rules, set_layer_visibility, footprint_primitives. All verified live on Bluetooth Sentinel (16–350 ms), zero dialogs.
  • Busy-notice reaper rewritten to go through ab's desktop verbs (find_window → find_control → ui_click) with a 4 s watch, after the X2-pid scan missed the notice twice on a live box.
  • Parked: create_net_class (IPCB_ObjectClass.SuperClass/.Name are undeclared in AD26 scripting) — function kept, not dispatched.
  • Last Python release of the bridge process: the next major moves the bridge to a Rust exe that calls ab's verbs and shrinks the DelphiScript agent to generic dump/apply primitives (ab#987 requests the binary runtime kind).

0.1.10 — 2026-08-31

  • #3 (Colby) root-cause FIX: the agent registers the PCB/SCH scripting APIs at startup (ServersUp: Client.StartServer('PCB')/('SCH') before the first PCBServer reference) — on a fresh session that never opened a PCB document the API unit is unregistered and the old template halted with an "Undeclared identifier: PostProcess" modal on the user's screen.
  • #3 follow-up: altium_agent_recover now closes the scratch AdomAgent_<digest>.PrjScr out of the Projects panel after freeing the engine (only there — after a successful start the resident loop runs from it).

0.1.9 — 2026-08-31

  • #2/PR #64 (Colby): detect the ADDevelop build — _TITLE_RE now accepts an optional edition word (Develop/Viewer/…) and optional version parens, with a guard so a bare mid-string "Altium Designer" in a dialog cannot pose as the main window. Proven against six title shapes.
  • #3 (Colby, partial): the -R helpers now reap Altium's single-instance "another instance is busy" notice in the background (both RunScript/RunScriptText lanes and recover), so a stray helper never leaves that modal on the user's screen.
  • Repo now carries the FULL source tree (handlers/, agent/, extension/, tools/) — previously only src/server.py was pushed, forcing PRs against the release zip.

0.1.8 — 2026-08-31

  • FIX: bridge.json verbs[] must be plain strings — 0.1.5–0.1.7 shipped four entries as objects, which ab's bridge_registry could not parse (the bridge vanished from the registry after its next reap while bridge_install still reported ok). Release assets are now uploaded explicitly (release create --binary-path does not attach).

0.1.7 — 2026-08-31

  • altium_install_library_bundle: the no-AI web-control lane (kicad-parity envelope, zip-slip hardened, restart contract, proof frame). Acceptance test: docs/webcontrol-altium-test.html.

0.1.6 — 2026-08-30

  • Full skill slate (10): added altium-install (the install recipe, finally packed), altium-libraries, altium-boards, altium-outputs, altium-bridge-dev, altium-bridge-publish — matching the kicad-bridge fleet depth.

0.1.5 — 2026-08-30

  • Barrett consolidation complete: cleanup_libs, set_visibility, xcopy_footprint (cross-library), and import_part (his fat verb on the resident lane) — full verb parity with barrett-land/adom-desktop-altium-bridge.
  • Skillpack: the pkg now ships altium-part-import (his 3-path catalog-part workflow, rewritten on these verbs, with the tri-vendor rule: Mouser preferred + DigiKey + LCSC part numbers on every component) and altium-kicad-import (his measured KiCad-10 findings + the vendored v10→v9 downgrader + the native no-importer lane).
  • Tools: kicad_v10_to_v9.py vendored from barrett-land/kicad10-to-altium.

0.1.4 — 2026-08-30

  • Barrett-parity library manipulation: clone_symbol (all display modes, 555 primitives verified), rename_symbol, read_symbol (pins/params/modes read-back), copy_footprint (pads+bodies+drawing), rename_footprint, remove_footprint. Smoke-tested on the LAN7800 libraries with zero dialogs.

0.1.3 — 2026-08-30

  • Symbol quality pass (John caught colliding 4-side names): tools/symbol_modes.py sizes bodies from measured pin-name lengths, std = CCW datasheet wrap (distinct from 2-column), die outline is a centred square skipped when it cannot fit; all ten modes frame-checked.
  • set_params/add_models parameters are hidden metadata (visible ones piled over the symbol).
  • New verb: altium_remove_symbol.

0.1.2 — 2026-08-30

  • LAN7800 creative-import lane: build_footprint (spec from tools/kicad2altium.py: via pads by duplicate designator, roundrect %, named mech layers incl. Adom.SolderBalls), build_symbol_modes (10 display modes on one component) + symbol_mode (editor-driven), add_models (N footprint models + params), normalize_bodies, test_board, board_shape (PCB:PlaceBoardOutline), swap_footprint (nets kept by designator), pcb_report with pads/bodies/nets/tracks.
  • Dialog pre-emption: ReadAll probes with FileOpen (the debugger breaks on any exception), TxClear on agent start / before save / after recover (dangling PreProcess = "a command is currently active").
  • Library canvas offset understood: bodies added through the canvas realign onto the pads automatically.

0.1.0 — 2026-08-30

  • Schematic placement fixed: place_on_sch copies each lib primitive onto a fresh sheet component (Replicate alone drops pins/body); proven on the CH340G (17 primitives render).
  • 3D pipeline fixed end-to-end: bodies persist (added via the PcbLib editor CANVAS with the footprint current), STEP + extruded both render on a real board; place_on_pcb builds a fresh eComponentObject, copies children BEFORE the board add, normalizes the pad cluster and each body to the target separately. Ray's CH340G STEP renders in 3D on a board.
  • New diagnostics: altium_pcb_report (per-component pads/bodies with layer+bbox, board census by ObjectId), place/agent stage breadcrumbs.
  • Agent lifecycle: agent_start now detects a STALE resident generation (gen stamp in the heartbeat) and auto-restarts it — redeployed agent code can no longer silently not load.

0.1.0 — 2026-08-29

  • Barrett merge: set_params (LCSC/MPN + footprint link), add_body (STEP/extruded), install_library, compile_intlib, extract_intlib; vendored AdomBridge extension (export_step/import_kicad — not loading on AD 26.9.1 yet).
  • Taskbar: Adom overlay badge while driving, measured progress bar on slow verbs, no AUMID stamping.
  • Phase 5: signin_status, license (WebView2 page driver), connect_workspace, a365_configure/status/query.
  • First cut (phases 0-4): readiness/status/diagnostics, resident DelphiScript agent (eval, run_command, state, open), dialogs by PID with screenshots, agent_recover, the eight show_* surfaces + demo, compile/ERC with Messages panel, DRC with violations, OutJob runner (Gerber/ODB++/NC drill/BOM proven on the Bluetooth Sentinel example), create_footprint / create_symbol / save.