← Commit history

plan: phases 2 and 3 verified live; phase 4 etiquette specification

John Lauer ·fa70825ad8 ·28d ago ·parent 050b74a
1 file changed +13
docs/rust-port-plan.md+13
@@ -14,6 +14,7 @@ Written 2026-09-11 from a full audit of the 0.9.350 source, the Adom Bridge (ab) | 2026-09-11 | Phase 1, third pass: 50 verbs. Schematic edit in place landed (place_symbol, wire, label, move, delete, set_property: lossless s-expression round trip, .bak per write, ERC before and after through kicad-cli with the delta reported; verified on KiCad 10.0.5, and service-kicad ERC accepts the edited sheets in the test suite). Install group landed (install_library, install_symbol, install_footprint, install_library_bundle, adom_library_heal, make_part_project, export_part). Misc group landed (ipc_api, export_molecule, errors, log_tail) plus the first-run seeding in core. Uninstall is the full Python contract (all-artifacts and testResidue modes, verified on ConfRoomROG). 119 tests. The native zip was installed through ab itself on ConfRoomROG (pinned cache, spawn kind exe) and the verb runner drove it over the relay: every ported, runner-visible verb passes (35 of 35, the rest skip by design), the 47 window and routing verbs fail as expected until their phases. One contract fix from that run: ab forwards bare verb names, and the binary now accepts both forms. Phase 2 (routing on the Rust IPC client) in progress. | | 2026-09-11 | Phase 2 landed in code: the nine routing verbs on `kicad-ipc-rs` (revision is the SHA-256 of the board as KiCad serialises it, one native commit per call, DRC on a disposable snapshot with `--refill-zones`), the four file-based routing verbs on a Rust port of the PCB parser (the routed file is byte-identical to the Python's apart from UUIDs), and `kicad_autoroute` with the ai engine returning a plan request and freerouting reserved for phase 2b. 139 tests; the six-net fixture routed on disk passes service-kicad DRC with zero errors. 59 verbs in the binary. Live IPC verification on ConfRoomROG is pending: the box dropped off the relay right after the phase 2 binary was staged (winvm went with it, which points at the network rather than the binary). Phase 3 started: the Windows and UI Automation crates cross-compile, the window-control surface is on the Platform trait, and the Windows implementation and the window verbs are being written in parallel. | | 2026-09-11 | Phase 3 landed in code: the Windows layer (window enumeration with hung detection, process queries, background spawn with SW_SHOWNOACTIVATE, posted keys and clicks, WM_SETTEXT, native menu walk plus WM_COMMAND, button clicks through BM_CLICK with a deadline then UI Automation, in-process UIA invoke and set-value, foreground push and the one sanctioned bring-to-front with a measured result, placement, PrintWindow capture for the canvas probe, idle time), every cross-process call gated on a WM_NULL probe so a hung KiCad can never hang the bridge; the ab callback client with forwarded caller identity; the KiCad window model and dialog tables; and the 20 window verbs (launch, the open family with the rungs recorded as `mechanism`, the close family with discardChanges through the save prompt, dismiss_dialogs, window_info, state, screenshot_all through ab, send_key, click, place_footprint, enable_software_opengl). Progress dialogs are never dismissed; SendInput is not in the native build at all. 79 verbs, 160 tests, exe 3.5 MB. Live verification of phases 2 and 3 waits on ConfRoomROG, which has been off the relay since the phase 2 binary was resumed there; winvm came back on its own, so the box needs a look. |+| 2026-09-11 | Phases 2 and 3 verified live. On arav-rog (KiCad 10.0.3): live routing state in 15 ms, dry run with snapshot DRC, commits as native undo steps, stale-revision refusal, vias, a crossing route rejected by DRC with nothing landed, removal verified, and both regression demo modes at zero errors and zero unconnected (the Python gate's numbers). On ConfRoomROG (KiCad 10.0.5) the full verb runner against the phase 3 binary: 61 pass, 18 fail, 16 skip, with every window verb passing and zero foreground steals. The 18 failures were verbs the binary did not carry yet (show family, progress, verb_times, upgrade, demo, the retired plugin verbs, the export_gerbers alias) plus one runner ordering bug, all addressed the same day: alias added, plugin verbs answer `plugin_retired` with the replacement verb, runner phases fixed, show and upgrade ports in progress. The server became multithreaded (six workers, a GUI lock serialising window and IPC verbs) so status and progress polls answer in tens of milliseconds during a long verb. Two facts learned on the boxes: KiCad runs one API server per machine (first process wins; a standalone PCB editor beside the project manager gets none), now reported as `no_pcb_frame` with the remedy; and ab spawned a legacy `bridges\kicad` folder (bridge 0.9.65) on both boxes while its registry claimed the cache build, filed as [adom/adom-bridge#186](https://wiki.adom.inc/adom/adom-bridge/issues/186) and moved aside on both. |  ## The goal in one sentence @@ -194,6 +195,18 @@ Kyle owns the macOS build of Hydrogen and Adom Bridge and will take this codebas - **Today.** The headless half (kicad-cli, file editing, detection, settings paths) already works on all three because it only needs the paths each OS file provides. `cargo check --target aarch64-apple-darwin` and `--target x86_64-unknown-linux-musl` pass from the container; the Windows binary cross-builds from Linux with mingw-w64. - **Publishing.** ab manifests are single-zip, so each OS ships from its own build until per-platform manifests exist. Windows first; macOS and Linux follow when their platform files reach parity, gated by the same verb runner. +## Phase 4 specification: the etiquette loop++What the Python `handlers/win_focus.py` learned over a month, restated as the contract the native loop must meet. It runs inside the binary on a dedicated thread pair; if ab-core takes [adom/adom-bridge#184](https://wiki.adom.inc/adom/adom-bridge/issues/184), the same contract moves there.++- **Two detectors, one action.** A WinEventHook on EVENT_SYSTEM_FOREGROUND and EVENT_OBJECT_SHOW (out of context, skip own process) pumping messages on its own thread, plus a 120 ms polling sentinel as the fallback. Both funnel into one decision function.+- **The decision.** A KiCad window took the foreground. Bounce it only if all of these hold: a verb is in flight or a guard is armed; the window is bridge-owned (pid ledger: spawned pids plus their children, persisted to disk so a restart still knows); it is not inside a sanctioned foreground window (`bring_to_front` with a reason); the user did not click it (mouse buttons down with the cursor over it or over the shell), did not alt-tab to it, and is not past the foreground-lock idle timeout (the idle rule: when the user has been idle, Windows grants the foreground and the bridge must not fight it).+- **The action.** Exactly one `push_to_background` per (hwnd, pid), recorded in a ledger with an expiry; never minimize, never move off screen, never touch a hung window. A 3 s no-activate hold after a bounce, then released.+- **Guards.** Every window verb arms a guard for 20 s (120 s for the slow ones: 3D viewer, place footprint, demo, launch, show_3d_*), released early when the verb ends. The park baseline (windows already in the foreground before the verb) is never bounced.+- **Heals.** Every 20 s idle: clear stale no-activate styles the bridge set; drag windows back from off screen (placement heal, wiki #48: only when no part of the window is on any monitor). Every 60 s idle: strip bridge projects from `system.open_projects` in `kicad.json` while no KiCad runs.+- **Introspection.** `kicad_state.focusDebug` and `focusEvents` (ring buffer of every transition with its classification), the same fields the Python exposes.+- **Never.** No SetWindowsHookEx of any kind, no global input listeners, no SendInput in the loop, no style changes on a hung window (the 0.9.165 boot deadlock).+ ## Target architecture  ```