From 28b203d1e62224ccd378bc46913c49bb2e6d5a5a Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 26 Mar 2026 15:44:21 -0700 Subject: [PATCH 1/8] Fix an assertion failure bug when scaling a line in the transform cage --- .../tool/common_functionality/transformation_cage.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index dba65b4080..5f0ff7a1e1 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -212,16 +212,15 @@ impl SelectedEdges { let original_from_pivot = updated - pivot; // The original vector from the point to the pivot let mut scale_factor = new_from_pivot / original_from_pivot; - // Constrain should always scale by the same factor in x and y + // Constrain should always scale by the same factor in x and y. + // When one axis of `original_from_pivot` is near zero (e.g. for a line's degenerate bounding box), + // the scale factor for that axis is numerically unstable, so we copy from the more stable axis. if constrain { - // When the point is on the pivot, we simply copy the other axis. - if original_from_pivot.x.abs() < 1e-5 { + if original_from_pivot.x.abs() < original_from_pivot.y.abs() { scale_factor.x = scale_factor.y; - } else if original_from_pivot.y.abs() < 1e-5 { + } else { scale_factor.y = scale_factor.x; } - - debug_assert!((scale_factor.x - scale_factor.y).abs() < 1e-5); } if !(self.left || self.right || constrain) { From a25a6642e974514c58e2875b7c8698b75d1b7d1c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 28 Mar 2026 02:30:22 -0700 Subject: [PATCH 2/8] Fix missing defaults on node gradient inputs --- node-graph/graph-craft/src/document/value.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 91b6831809..7126db8a66 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -127,6 +127,9 @@ macro_rules! tagged_value { // Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned. Some(match concrete_type.id? { x if x == TypeId::of::<()>() => TaggedValue::None, + // Table-wrapped types need a single-row default with the element's default, not an empty table + x if x == TypeId::of::>() => TaggedValue::Color(Table::new_from_element(Color::default())), + x if x == TypeId::of::>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())), $( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )* _ => return None, }) From c38b98af1656d5f9de050638cd35c39da41b063b Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 28 Mar 2026 04:23:26 -0700 Subject: [PATCH 3/8] Fix Blend Shapes path input wire not updating to show in the UI after Layer > Blend --- .../portfolio/document/utility_types/network_interface.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 3c031a39b3..a1fd08e4f0 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -4005,6 +4005,14 @@ impl NodeNetworkInterface { } } (_, NodeInput::Node { node_id: upstream_node_id, .. }) => { + // If the old input wasn't exposed but the new one is (`Node` inputs are always exposed), + // the node's port count changed, so its click targets need to be recomputed + if !old_input.is_exposed() + && let InputConnector::Node { node_id, .. } = input_connector + { + self.unload_node_click_targets(node_id, network_path); + } + // Load structure if the change is to the document network and to the first or second if network_path.is_empty() { if matches!(input_connector, InputConnector::Export(0)) { From 52d167c89a7b9b4ab4356acc6efbf744bbe7b51f Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 28 Mar 2026 15:45:14 -0700 Subject: [PATCH 4/8] Fix assertion failure due to browser non-monotonic timestamp --- editor/src/messages/input_mapper/utility_types/misc.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index ea0bfc0049..e98df2a22e 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -139,7 +139,8 @@ impl FrameTimeInfo { } pub fn advance_timestamp(&mut self, next_timestamp: Duration) { - debug_assert!(next_timestamp >= self.timestamp); + // Guard against non-monotonic timestamps from the browser (Keavon observed this once in Chrome) + let next_timestamp = next_timestamp.max(self.timestamp); self.prev_timestamp = Some(self.timestamp); self.timestamp = next_timestamp; From 66204f55869d554da5f7aa189a83fd4042637144 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 28 Mar 2026 16:09:47 -0700 Subject: [PATCH 5/8] Fix SVG renderer drawing 1px strokes as half-width when using stroke alignment --- node-graph/libraries/rendering/src/render_ext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/libraries/rendering/src/render_ext.rs b/node-graph/libraries/rendering/src/render_ext.rs index f455f719b1..192121c405 100644 --- a/node-graph/libraries/rendering/src/render_ext.rs +++ b/node-graph/libraries/rendering/src/render_ext.rs @@ -113,7 +113,7 @@ impl RenderExt for Stroke { } // Set to None if the value is the SVG default - let weight = (self.weight != 1.).then_some(self.weight); + let weight = (self.weight != if self.align == StrokeAlign::Center { 1. } else { 1. / 2. }).then_some(self.weight); let dash_array = (!self.dash_lengths.is_empty()).then_some(self.dash_lengths()); let dash_offset = (self.dash_offset != 0.).then_some(self.dash_offset); let stroke_cap = (self.cap != StrokeCap::Butt).then_some(self.cap); From ea67a2177b6aadf4aef07679a05d869700539df1 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 28 Mar 2026 16:13:46 -0700 Subject: [PATCH 6/8] Fix incorrect appearance of the ColorInput widget when set to "none" and "disabled" --- frontend/src/components/widgets/inputs/ColorInput.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/widgets/inputs/ColorInput.svelte b/frontend/src/components/widgets/inputs/ColorInput.svelte index f0d2b2a699..3b8bac24de 100644 --- a/frontend/src/components/widgets/inputs/ColorInput.svelte +++ b/frontend/src/components/widgets/inputs/ColorInput.svelte @@ -116,7 +116,7 @@ background-repeat: var(--color-transparent-checkered-background-repeat); } - &:not(.disabled).none > button { + &.none > button { background: var(--color-none); background-repeat: var(--color-none-repeat); background-position: var(--color-none-position); @@ -132,6 +132,7 @@ left: 0; right: 0; background: var(--color-4-dimgray); + opacity: 0.5; } &:not(.disabled):hover > button .text-label, From 6862e8967cb6ec5b883781447ba79121eada7691 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 28 Mar 2026 16:14:34 -0700 Subject: [PATCH 7/8] Fix lerp function in Fill enum to handle None cases correctly --- node-graph/libraries/vector-types/src/vector/style.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node-graph/libraries/vector-types/src/vector/style.rs b/node-graph/libraries/vector-types/src/vector/style.rs index b5a4ceb52f..8a9298e910 100644 --- a/node-graph/libraries/vector-types/src/vector/style.rs +++ b/node-graph/libraries/vector-types/src/vector/style.rs @@ -64,8 +64,8 @@ impl Fill { pub fn lerp(&self, other: &Self, time: f64) -> Self { let transparent = Self::solid(Color::TRANSPARENT); - let a = if *self == Self::None { &transparent } else { self }; - let b = if *other == Self::None { &transparent } else { other }; + let a = if *self == Self::None && *other != Self::None { &transparent } else { self }; + let b = if *other == Self::None && *self != Self::None { &transparent } else { other }; match (a, b) { (Self::Solid(a), Self::Solid(b)) => Self::Solid(a.lerp(b, time as f32)), @@ -82,7 +82,7 @@ impl Fill { Self::Gradient(a.lerp(b, time)) } (Self::Gradient(a), Self::Gradient(b)) => Self::Gradient(a.lerp(b, time)), - _ => Self::None, + (Self::None, _) | (_, Self::None) => Self::None, } } From b82a644833aae0094570aea5a2331bbee63296fe Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 28 Mar 2026 16:38:14 -0700 Subject: [PATCH 8/8] Fix stroke alignment bug --- node-graph/libraries/rendering/src/render_ext.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node-graph/libraries/rendering/src/render_ext.rs b/node-graph/libraries/rendering/src/render_ext.rs index 192121c405..d7736f804b 100644 --- a/node-graph/libraries/rendering/src/render_ext.rs +++ b/node-graph/libraries/rendering/src/render_ext.rs @@ -112,8 +112,10 @@ impl RenderExt for Stroke { return String::new(); } + let default_weight = if self.align != StrokeAlign::Center && render_params.aligned_strokes { 1. / 2. } else { 1. }; + // Set to None if the value is the SVG default - let weight = (self.weight != if self.align == StrokeAlign::Center { 1. } else { 1. / 2. }).then_some(self.weight); + let weight = (self.weight != default_weight).then_some(self.weight); let dash_array = (!self.dash_lengths.is_empty()).then_some(self.dash_lengths()); let dash_offset = (self.dash_offset != 0.).then_some(self.dash_offset); let stroke_cap = (self.cap != StrokeCap::Butt).then_some(self.cap);