From 1b79120568b10ff570856c3796924fe08d79a7c1 Mon Sep 17 00:00:00 2001 From: Vikram Bhaskaran Date: Mon, 12 Oct 2026 23:41:01 +1100 Subject: [PATCH] Report compound-path paint and a non-normal blend in document summaries. document.inspect skipped fill and stroke on compound paths because they are containers, and it never wrote the blend mode. --- crates/engine/src/inspect.rs | 7 ++++++- crates/engine/src/tests.rs | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/inspect.rs b/crates/engine/src/inspect.rs index a43044472..11fca08a2 100644 --- a/crates/engine/src/inspect.rs +++ b/crates/engine/src/inspect.rs @@ -1,6 +1,7 @@ //! Agent-friendly document summaries and view-models for panels. use serde_json::{Value, json}; +use vectorcraft_color::BlendMode; use vectorcraft_doc::{ArrowAlign, Document, LineCap, LineJoin, Node, NodeKind, StrokeAlign, StrokeLayer, WidthProfile}; use crate::Session; @@ -40,6 +41,9 @@ pub fn node_summary_opts(n: &Node, opts: SummaryOpts) -> Value { if n.opacity < 1.0 { v["opacity"] = json!(n.opacity); } + if n.blend != BlendMode::Normal { + v["blend"] = json!(n.blend.label()); + } // The angle of its rotated bounding box (counter-clockwise degrees). if n.bbox_angle != 0.0 { v["rotation"] = json!(n.bbox_angle); @@ -58,7 +62,8 @@ pub fn node_summary_opts(n: &Node, opts: SummaryOpts) -> Value { v["objectStroke"] = json!(n.appearance.stroke_paint().label()); v["objectStrokeWidth"] = json!(n.appearance.stroke_width()); } - } else if !n.is_container() { + } else if !n.is_container() || matches!(n.kind, NodeKind::Compound { .. }) { + // A compound path is a container, but its fill and stroke live on it, not on its members. v["fill"] = json!(n.appearance.fill_paint().label()); v["stroke"] = json!(n.appearance.stroke_paint().label()); v["strokeWidth"] = json!(n.appearance.stroke_width()); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 585ffd121..ef397c570 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1520,6 +1520,28 @@ fn inspect_lists_everything() { /// `document.node {summary: true}` answers the `document.inspect` shape for one node: /// the same fields the full-tree summary carries, without serializing the whole tree. #[test] +/// A compound path reports the paint that lives on it, and any node reports a blend other than Normal. +#[test] +fn inspect_reports_compound_paint_and_blend() { + let mut s = session(); + let a = rect(&mut s, 0.0, 0.0, 40.0, 40.0); + let b = rect(&mut s, 10.0, 10.0, 10.0, 10.0); + s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap(); + let c = s.execute("object.compoundPath.make", &json!({})).unwrap()["id"].as_u64().unwrap(); + s.execute("object.setProps", &json!({"id": c, "blend": "Multiply"})).unwrap(); + let v = s.execute("document.inspect", &json!({})).unwrap(); + let node = &v["layers"][0]["children"][0]; + assert_eq!(node["id"], c); + assert_eq!(node["kind"], "Compound Path"); + assert!(node["fill"].as_str().is_some_and(|f| f != "None"), "compound paint: {node}"); + assert!(node.get("stroke").is_some() && node.get("strokeWidth").is_some(), "{node}"); + assert_eq!(node["blend"], "Multiply"); + let plain = rect(&mut s, 80.0, 0.0, 10.0, 10.0); + let v = s.execute("document.inspect", &json!({})).unwrap(); + let plain = v["layers"][0]["children"].as_array().unwrap().iter().find(|n| n["id"] == plain.0).unwrap(); + assert!(plain.get("blend").is_none(), "Normal is omitted: {plain}"); +} + fn document_node_summary_matches_inspect() { let mut s = session(); let id = rect(&mut s, 0.0, 0.0, 10.0, 10.0).0;