AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
macOS: find the PCB editor by its title when the KiCad bridge names no kind #65
Versions: base 01ab14c4ae88b263bf4d135e7c115f269d89912b (Publish 0.2.50) · ab 1.9.232 (macOS, standalone; Hydrogen not involved) · macOS KiCad bridge 0.11.3 · KiCad 10.0.7 · relay adom-bridge 2.1.185
Problem. On macOS the KiCad bridge lists kicad_window_info editors as {app, hwnd, pid, rect, title}, with no kind (the board's editor beside a bare "PCB Editor" strip and the "3D Viewer"). 0.2.38 (#384) already matches the 3D Viewer by its title; the PCB editor is still looked up only by kind. So Bridge::pcb_editor_hwnd() is always None on a Mac, and every caller falls back to the hwnd stored in run.json: tour nets (its zoom target and the per-net screenshots), tour 3d, capture start, and a step's clip after window_not_found. Because Run::save sorts captures by start and the {opened, hwnd} record has none, that stored hwnd is the last capture open's window, right only while KiCad was never reopened outside capture open. When it was (KiCad closed for tour fields and reopened by hand, a crash, the user), the record names a window that is gone:
capture start:ERROR: window recorder did not start: {"error":"window 1111 matched nothing","errorCode":"window_not_found"};tour nets:OK: net walkthrough recorded: 3 nets, each selected as a whole ... and framed, while everykicad_send_keyanddesktop_screenshot_windowwent to the dead window and failed: no framing, no per-net screenshot, no clip.
Change (crates/aiflow-bridge/src/lib.rs). pcb_editor_hwnd() reads pcb_editor_of(reply):
- the editor whose
kindispcb_editor, as before; - None when the bridge names kinds and lists no PCB editor, as before;
- only when no editor carries a kind: the first window titled with a board name and "PCB Editor" (KiCad's macOS title puts an em dash between them; an unsaved board's name starts with
*), never the bare "PCB Editor" strip or the "3D Viewer".
The Windows and Linux bridges name kinds, so they read as before. No check, threshold, gate or proof changes.
Tests on 0.2.50, built together with the project-page PR (which touches other files):
pcb_editor_tests, 3 new: the Mac reply from a real run, an unsaved board's title, the viewer and the bare strip never taken, kind-named replies unchanged.cargo test --workspace: 578 passed, 0 failed, 11 ignored (base: 575, 0, 11). The 25 scripts in tests/ exit as on the base.- End to end with a stand-in
adom-bridgethat answers exactly like the macOS bridge (no machine), with the stored editor gone (1111) and the live one at 2846: 0.2.50 sends every call to 1111 (capture startwindow_not_found;tour netsreports "framed" with all six window calls failed). With this changecapture startrecords 2846, every zoom and screenshot call oftour netsreaches 2846, andcapture stoppulls the clip. The ordinary order (capture open, tour 3d, capture start, tour nets, capture stop) is unchanged. - tools/pr_preflight.py against
01ab14c4: no problems, ready for human diff review.
Related, for the same tour: tour nets discards the replies of its zoom calls and screenshots and still says "framed" (as above). kicad-bridge #121 found on Windows that a menu command posted to a background editor is delivered and the view does not move; it was closed with kicad_zoom_to (KiCad Bridge 1.0.80, Windows and Linux). Suggest using kicad_zoom_to where the bridge has it, falling back to kicad_send_key {menu}, and saying "framed" only when a zoom call succeeded. The macOS KiCad bridge's own select_net and menu faults are fixed in adom/kicad-bridge#64.
Diff Skip to comments (1)
@@ -1,1052 +1,1130 @@⋯ 292 unchanged lines ⋯ result } +/// The PCB editor's window in a `kicad_window_info` reply: the editor whose `kind` is `pcb_editor`.+/// The macOS KiCad bridge (0.11.x) names no kind at all (`{app, hwnd, pid, rect, title}`), so there,+/// and only when no editor carries a kind, it is the first window titled `<board> — PCB Editor`.+/// Before this, a Mac run fell back to the last window it had filmed, which+/// after `tour fields` or `tour 3d` is the closed Fields or 3D Viewer window: the net walkthrough+/// then framed and screenshotted nothing, and the next step's clip did not start.+pub fn pcb_editor_of(reply: &Value) -> Option<i64> {+ let d = reply.get("data").cloned().unwrap_or(reply.clone());+ let eds = d.get("editors").and_then(|e| e.as_array())?;+ let hwnd = |w: &Value| w.get("hwnd").and_then(|h| h.as_i64());+ if let Some(w) = eds.iter().find(|w| w.get("kind").and_then(|k| k.as_str()) == Some("pcb_editor")) {+ return hwnd(w);+ }+ // a bridge that names kinds and lists no PCB editor has none open: unchanged+ if eds.iter().any(|w| w.get("kind").map_or(false, |k| !k.is_null())) {+ return None;+ }+ eds.iter().find(|w| w.get("title").and_then(|t| t.as_str()).map_or(false, titled_pcb_editor)).and_then(hwnd)+}++/// "scratch_strip — PCB Editor" or "*board — PCB Editor" (unsaved), never the bare+/// "PCB Editor" strip the macOS bridge also lists, nor the "3D Viewer".+fn titled_pcb_editor(title: &str) -> bool {+ title.trim().strip_suffix("PCB Editor").map_or(false, |head| {+ !head.trim_end_matches(|c: char| c.is_whitespace() || matches!(c, '—' | '–' | '-' | '|')).trim_start_matches('*').trim().is_empty()+ })+}+ #[derive(Clone)] pub struct Bridge { pub ai_thread: String,⋯ 170 unchanged lines ⋯ } pub fn pcb_editor_hwnd(&self) -> Option<i64> {- let r = self.call("kicad_window_info", &json!({}));- let d = r.get("data").cloned().unwrap_or(r.clone());- d.get("editors").and_then(|e| e.as_array()).and_then(|a| a.iter().find(|w| w.get("kind").and_then(|k| k.as_str()) == Some("pcb_editor"))).and_then(|w| w.get("hwnd")).and_then(|h| h.as_i64())+ pcb_editor_of(&self.call("kicad_window_info", &json!({}))) } pub fn record_start(&self, reason: &str) -> Result<String, Value> {⋯ 572 unchanged lines ⋯ assert!(fine.get("offendingRefs").is_none()); } }++#[cfg(test)]+mod pcb_editor_tests {+ use super::*;++ // kicad_window_info on the Mac (KiCad bridge 0.11.3, KiCad 10.0.7) with the 3D Viewer open, as the+ // Rev C strip run read it on 2026-11-02 (.aiflow-run-revc-edge-board/stop/kicad-window-info-3d-viewer-open.json)+ fn mac_reply() -> Value {+ json!({"data": {"autoDismissed": [], "editors": [+ {"app": "PCB Editor", "hwnd": 2893, "pid": 52411, "rect": {"height": 720, "width": 1280}, "title": "3D Viewer"},+ {"app": "PCB Editor", "hwnd": 2858, "pid": 52411, "rect": {"height": 44, "width": 1470}, "title": "PCB Editor"},+ {"app": "PCB Editor", "hwnd": 2846, "pid": 52411, "rect": {"height": 923, "width": 1470}, "title": "edge_board — PCB Editor"}],+ "hasModalDialogs": false, "modalDialogs": [], "projectManager": null},+ "output": "3 editor(s), 0 dialog(s)", "status": "ok", "success": true})+ }++ #[test]+ fn the_mac_editor_is_found_by_its_title() {+ assert_eq!(pcb_editor_of(&mac_reply()), Some(2846));+ // unsaved board: KiCad marks the title with a star+ let star = json!({"data": {"editors": [{"hwnd": 7, "title": "PCB Editor"}, {"hwnd": 8, "title": "*board — PCB Editor"}]}});+ assert_eq!(pcb_editor_of(&star), Some(8));+ }++ #[test]+ fn never_the_viewer_or_the_bare_strip() {+ let only_viewer = json!({"data": {"editors": [{"hwnd": 2893, "title": "3D Viewer"}, {"hwnd": 2858, "title": "PCB Editor"}]}});+ assert_eq!(pcb_editor_of(&only_viewer), None);+ assert!(!titled_pcb_editor("PCB Editor"));+ assert!(!titled_pcb_editor(" — PCB Editor"));+ assert!(!titled_pcb_editor("3D Viewer"));+ assert!(!titled_pcb_editor("board — Schematic Editor"));+ assert!(titled_pcb_editor("scratch_strip — PCB Editor"));+ }++ #[test]+ fn a_bridge_that_names_kinds_is_read_as_before() {+ // the Windows bridge: kind decides, the title is never used+ let win = json!({"data": {"editors": [+ {"kind": "schematic_editor", "hwnd": 1, "title": "board — Schematic Editor"},+ {"kind": "pcb_editor", "hwnd": 2, "title": "board — PCB Editor"}]}});+ assert_eq!(pcb_editor_of(&win), Some(2));+ // kinds named and no PCB editor among them: none, even with a PCB-like title+ let none = json!({"data": {"editors": [{"kind": "3d_viewer", "hwnd": 3, "title": "board — PCB Editor"}]}});+ assert_eq!(pcb_editor_of(&none), None);+ // the reply without a data wrapper, and no editors at all+ assert_eq!(pcb_editor_of(&json!({"editors": [{"kind": "pcb_editor", "hwnd": 9}]})), Some(9));+ assert_eq!(pcb_editor_of(&json!({"status": "error"})), None);+ assert_eq!(pcb_editor_of(&json!({"data": {"editors": []}})), None);+ }+}+
Comments
Log in to comment.