← All Pull Requests

Return an explicit asynchronous refill acknowledgement without stale revision #14

Merged opened by John Lauer 2026-09-18
Merges astra/refill-acknowledgement → master

The separate refill call acknowledged KiCad's asynchronous command and immediately returned pre-fill measurements/revision as if completed. It now returns refillRequested:true, refilled:false and revisionUsable:false, with no revision or measurements. The caller must wait and make read-only zone-state calls. This takes the explicit reread option from PR #13's native review instead of guessing that a fixed sleep proves completion.

Base 87af1dc65f6fce292dc147f5cc32587bf5943d18 (published 1.0.23). Two-file focused branch change; no module/verb deletions. Production check and all 256 workspace tests pass (25 bridge + 231 core), including a response-contract regression. No shared runtime replaced. Native acceptance: refill request must return no revision/measurements; later read-only zone_state returns a usable snapshot after KiCad finishes. AI Flow consumer update follows to reread and require stable snapshots, including compatibility with the old synchronous-looking acknowledgement. This PR changes the refill response contract deliberately; ordinary refill:false readback remains unchanged.

Diff Skip to comments (1)

docs/native-zone-plan.md+3−1
@@ -6,7 +6,7 @@ Deletion absence is read only AFTER commit. `deletionVerified` is true on confir  ## Refill is a separate native operation -The zone-plan edit is one Undo step. It now defaults to no refill and returns `refilled:false`, `refillRequired:true` for a changed plan. Explicit `refill:true` refuses BEFORE mutation with `separate_refill_required`. After applying, call `kicad_zone_state` with `refill:true`, wait for completion, and use that operation's revision. Refilling can introduce its own Undo step. This intentionally changes the previous implicit-refill behavior that falsely reported one Undo step and a usable final revision.+The zone-plan edit is one Undo step. It now defaults to no refill and returns `refilled:false`, `refillRequired:true` for a changed plan. Explicit `refill:true` refuses BEFORE mutation with `separate_refill_required`. After applying, call `kicad_zone_state` with `refill:true`, treat its response as an acknowledgement, then wait and call `kicad_zone_state` with `refill:false`. Use the later read-only snapshot's revision; the refill acknowledgement deliberately has no revision or copper measurements. Refilling can introduce its own Undo step. This intentionally changes the previous implicit-refill behavior that falsely reported one Undo step and a usable final revision.  ## Equality after native serialization @@ -15,3 +15,5 @@ Known non-geometric serialization differences are normalized: order of metadata, ## Native acceptance required  On a disposable board: create, reapply the identical owned plan (expect unchanged IDs and zero Undo steps), update it, remove it, and undo each mutation once. Verify removed IDs absent after commit, foreign zones intact, and revision equals a fresh state read. Verify `refill:true` refuses without edits; run separate refill and read its revision. Repeat silkscreen removal with duplicate strings and a mixed create/update/remove batch, then one Undo. Source tests alone do not establish these native results.++A refill acknowledgement has `refillRequested:true`, `refilled:false`, `revisionUsable:false`. It is not a completed snapshot. Retry read-only requests while KiCad is busy; do not repeat the refill mutation after a timeout.
rust/crates/kicad-core/src/ipc.rs+21−3
@@ -1081,15 +1081,23 @@ pub fn remove_zone(ctx: &Ctx, args: &Value) -> RResult<Value> {     Ok(result) } +fn refill_requested(board_path: &str) -> Value {+    json!({+        "success": true, "boardPath": board_path, "source": "live-editor",+        "refillRequested": true, "refilled": false, "revisionUsable": false,+        "_hint": "Refill was requested, not confirmed complete. KiCad fills asynchronously. Wait and call kicad_zone_state with refill:false; retry read-only calls while busy. Use the later snapshot's revision, never replay refill after a timeout. No revision or copper measurements are returned in this acknowledgement."+    })+}+ /// `kicad_zone_state`: every zone on the live board with its filled copper area per layer /// (KiCad's own fill polygons), the sum per layer and the coverage against the outline /// box. `refill:true` refills every zone first (the fill can be stale after routing). pub fn zone_state(ctx: &Ctx, args: &Value) -> RResult<Value> {     let s = connect(ctx, args)?;-    let mut refilled = false;     if args.get("refill") == Some(&json!(true)) {         s.client.refill_zones(Vec::new()).map_err(map_err)?;-        refilled = true;+        // Acknowledgement precedes asynchronous fill; no stale snapshot escapes.+        return Ok(refill_requested(&s.file_path));     }     let (text, data, revision) = s.snapshot()?;     let raws = s.client.get_items_raw_by_type_codes(vec![PcbObjectTypeCode::new_zone().code]).map_err(map_err)?;@@ -1099,7 +1107,7 @@ pub fn zone_state(ctx: &Ctx, args: &Value) -> RResult<Value> {     }     let mut out = json!({         "success": true, "boardPath": s.file_path, "revision": revision, "source": "live-editor",-        "kicadVersion": s.version.full_version, "socket": s.socket, "refilled": refilled,+        "kicadVersion": s.version.full_version, "socket": s.socket, "refilled": false,         "_hint": "filledAreaByLayerMm2 is the copper KiCad's zones keep on each layer: for a copper-ablation process, what does not have to be milled away. Add pours with kicad_add_zone; kicad_routing_validate is the DRC.",     });     merge(&mut out, &zones::state_json(&read, &data));@@ -1331,6 +1339,16 @@ fn merge(into: &mut Value, from: &Value) {  #[cfg(test)] mod tests {+    #[test]+    fn refill_acknowledgement_never_certifies_a_stale_snapshot() {+        let reply = super::refill_requested("board.kicad_pcb");+        assert_eq!(reply["refillRequested"], true);+        assert_eq!(reply["refilled"], false);+        assert_eq!(reply["revisionUsable"], false);+        assert!(reply.get("revision").is_none());+        assert!(reply.get("filledAreaByLayerMm2").is_none());+    }+     #[test]     fn zone_plan_refill_refuses_before_ipc_or_mutation() {         let error = super::apply_zone_plan(&super::Ctx::default(), &serde_json::json!({

Comments

John Lauer 2026-09-18

Merged on the wiki (4888536), shipped as KiCad Bridge 1.0.24 (insiders), accepted on ConfRoomROG.

  • kicad_zone_state {refill:true} on a 57-zone board: success:true, refillRequested:true, refilled:false, revisionUsable:false, no revision, no filledAreaByLayerMm2. Exactly the acknowledgement.
  • A read-only kicad_zone_state 6 s later was refused (KiCad still filling), which is the documented "retry read-only calls while busy". The retry a few seconds after that returned a usable snapshot: revision 09b0543c, 57 zones, copper per layer.

Two texts the PR did not touch still described the old one-call contract, so I changed them in the same release: the verb's catalog hint for kicad_zone_state, and step 5 of the kicad-copper-pours skill (measure with refill:true, wait, then read with refill:false). Worth a grep for the same phrase in AI Flow before its consumer update lands, since that is where the one-call measure lived.

Closing.

Log in to comment.