Inspect: report each layer's vector mask (#2980)

`hasMask` in document inspection covers only raster masks, so a layer
with a vector mask looked unmasked to an agent. Layers, groups included,
now also report `hasVectorMask` and `vectorMaskEnabled` (null when there
is no vector mask). `hasMask` keeps its meaning.
This commit is contained in:
KnottyDyes authored and GitHub committed 2026-10-11 15:21:03 -04:00
1 parent 28874bbfad
commit 2d6ff3f6cc
2 files changed
+58

No files matched your search

+55
View File
@@ -81,6 +81,8 @@ fn layer_sel(l: &Layer, selected: &[photocraft_doc::LayerId]) -> Value {
"blend": l.blend.label(),
"clipped": l.clipped,
"hasMask": l.mask.is_some(),
"hasVectorMask": l.vector_mask.is_some(),
"vectorMaskEnabled": l.vector_mask.as_ref().map(|mask| mask.enabled),
"bounds": bounds,
"selected": selected.contains(&l.id),
"linkGroup": l.link_group,
@@ -154,3 +156,56 @@ fn layer_sel(l: &Layer, selected: &[photocraft_doc::LayerId]) -> Value {
}
v
}
#[cfg(test)]
mod tests {
use super::*;
use photocraft_color::PixelFormat;
use photocraft_doc::{LayerMask, Path, VectorMask};
#[test]
fn document_inspect_reports_raster_and_vector_masks_when_nested() {
let cases = [(false, None), (true, None), (false, Some(true)), (false, Some(false)), (true, Some(true))];
let mut session = Session::new();
session.execute("file.new", json!({"width": 1, "height": 1})).unwrap();
let layers = cases
.iter()
.map(|&(raster, enabled)| {
let mut layer = Layer::raster("Mask case", PixelFormat::RGBA8);
layer.mask = raster.then(LayerMask::reveal_all);
layer.vector_mask = enabled.map(|enabled| VectorMask { enabled, ..VectorMask::new(Path::default()) });
layer
})
.collect::<Vec<_>>();
let mut group = Layer::group("Inner", layers.clone());
group.vector_mask = Some(VectorMask { enabled: false, ..VectorMask::new(Path::default()) });
let mut roots = layers;
roots.push(Layer::group("Outer", vec![group]));
session
.edit("Mask fixtures", |doc, _| {
doc.layers = roots;
Ok(())
})
.unwrap();
let inspected = session.execute("document.inspect", json!({})).unwrap();
let roots = inspected["layers"].as_array().unwrap();
let outer = &roots[0];
let inner = &outer["children"][0];
assert_eq!(outer.get("hasMask"), Some(&json!(false)));
assert_eq!(outer.get("hasVectorMask"), Some(&json!(false)));
assert_eq!(outer.get("vectorMaskEnabled"), Some(&Value::Null));
assert_eq!(inner.get("hasMask"), Some(&json!(false)));
assert_eq!(inner.get("hasVectorMask"), Some(&json!(true)));
assert_eq!(inner.get("vectorMaskEnabled"), Some(&json!(false)));
for nodes in [&roots[1..], inner["children"].as_array().unwrap().as_slice()] {
assert_eq!(nodes.len(), cases.len());
for (node, &(raster, enabled)) in nodes.iter().zip(cases.iter().rev()) {
assert_eq!(node.get("hasMask"), Some(&json!(raster)));
assert_eq!(node.get("hasVectorMask"), Some(&json!(enabled.is_some())));
assert_eq!(node.get("vectorMaskEnabled"), Some(&json!(enabled)));
}
}
}
}
+3
View File
@@ -18,6 +18,9 @@ described in [development.md](development.md). UI tools require a running deskto
- `command_batch`: ordered `steps`, optional `stop_on_error`; one history step per command,
not an atomic transaction. Existing per-step results and reply budgets are preserved.
- `doc_inspect`: layer tree, history and selection; an empty session returns `document:null`.
Every layer (including groups and nested children) reports raster-only `hasMask`,
`hasVectorMask` for vector mask presence, and `vectorMaskEnabled` (`true`/`false` when
present, `null` when absent). A disabled vector mask still has `hasVectorMask:true`.
- `render_preview`: bounded PNG content; `index` and `max_side` (default 1024, maximum 2048).
- `ui_inspect` and `ui_screenshot`: live app state/window in bridge mode.