#400: a nested batch keeps its own step references

The outer batch passes a command.batch step on as written, so the inner
steps' `$step` references point at the inner batch's steps. The docs say
that `$step` is a reserved key in step params.
This commit is contained in:
bflatastic
2026-10-15 18:30:05 +07:00
parent 6d9024e0d0
commit e11a182afd
4 changed files with 32 additions and 5 deletions
+21
View File
@@ -89,3 +89,24 @@ fn text_that_looks_like_a_reference_is_left_alone() {
let n = s.doc().unwrap().node(id(&results[0])).unwrap().clone();
assert!(matches!(n.kind, figcraft_doc::NodeKind::Text(t) if t.characters == "$step 0 {\"$step\": 0}"));
}
#[test]
fn a_nested_batch_refers_to_its_own_steps() {
let mut s = Session::new();
let frame = |name: &str| json!({"command": "node.create", "params": {"type": "frame", "name": name, "width": 100, "height": 100}});
let results = s
.execute(
"command.batch",
&json!({"commands": [
frame("Outer"),
{"command": "command.batch", "params": {"commands": [
frame("Inner"),
{"command": "node.create", "params": {"type": "rectangle", "parent": {"$step": 0}, "width": 10, "height": 10}}
]}}
]}),
)
.unwrap();
let inner = &results[1];
let rect = s.doc().unwrap().node(id(&inner[1])).unwrap().clone();
assert_eq!(rect.parent, Some(id(&inner[0])), "in the nested batch's own first frame, not the outer one");
}
+4 -2
View File
@@ -1124,12 +1124,14 @@ impl Session {
/// Run several commands as one undo step. A param value `{"$step": n}` is replaced by the `id`
/// in the result of the earlier step `n` (`{"$step": n, "field": "…"}` picks another field),
/// so a step can target a layer that an earlier step of the same batch created.
/// so a step can target a layer that an earlier step of the same batch created. Any object
/// with a `$step` key in a step's params is such a reference, except inside a nested
/// `command.batch` step: its references are to its own steps.
pub fn batch(&mut self, label: &str, cmds: &[(String, Value)]) -> Result<Vec<Value>> {
self.grouped(label, |s| {
let mut results: Vec<Value> = Vec::with_capacity(cmds.len());
for (step, (id, p)) in cmds.iter().enumerate() {
let p = resolve_step_refs(p, &results, step, 0)?;
let p = if id == "command.batch" { p.clone() } else { resolve_step_refs(p, &results, step, 0)? };
results.push(s.execute(id, &p)?);
}
Ok(results)
+3 -2
View File
@@ -63,8 +63,9 @@ JSON or the gradient shorthand `{"type":"linearGradient","stops":["#hex","#hex"]
```
Inside `command.batch`, `{"$step": n}` in a step's params stands for the `id` in step `n`'s result
(zero-based; `{"$step": n, "field": "…"}` for another field), so one batch can create a frame and
fill it:
(zero-based; `{"$step": n, "field": "…"}` for another field; any object with a `$step` key is read
as one, and a nested `command.batch` step keeps its own references to its own steps), so one batch
can create a frame and fill it:
```json
{"id":4,"method":"engine.execute","params":{"command":"command.batch","params":{"commands":[{"command":"node.create","params":{"type":"frame","width":200,"height":100}},{"command":"node.create","params":{"type":"rectangle","parent":{"$step":0},"width":20,"height":20}}]}}}
+4 -1
View File
@@ -1532,7 +1532,10 @@ result. This builds a nested tree in one call and one undo step, without guessin
A reference to the same or a later step, a negative or non-integer index, a missing `field` or any
other key next to `$step` fails the whole batch, which is rolled back. Only an object whose keys are
`$step` (and optionally `field`) is a reference: text such as `"$step 0"` is left as written.
`$step` (and optionally `field`) is a reference: text such as `"$step 0"` is left as written. The
`$step` key is reserved in step params: an object holding it anywhere (in plugin data or a variable
value too) is read as a reference. A step that is itself a `command.batch` is passed on as written,
so the references in its steps are to its own steps, not the outer batch's.
The end-to-end tests in `crates/mcp/tests/agent_tasks.rs` are worked examples of realistic tasks
(auto layout, a card built in one batch with step references, components and overrides, gradients