From 190ae9863850c218cf37d00e3ad67784e73346f4 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 2 Mar 2026 21:45:50 -0800 Subject: [PATCH 1/2] Fix regression where Vello doesn't render new document opened after closing all documents --- editor/src/messages/portfolio/portfolio_message_handler.rs | 4 ++++ editor/src/node_graph_executor.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index e73eb4ef98..1b4c9540c4 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1088,6 +1088,10 @@ impl MessageHandler> for Portfolio // Set the new active document ID self.active_document_id = Some(document_id); + // Clear the cached canvas frame so the frontend gets a fresh SVG with the canvas placeholder, + // since the previous document's DOM element was destroyed when the document view changed. + self.executor.clear_canvas_cache(); + responses.add(MenuBarMessage::SendLayout); responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(FrontendMessage::UpdateActiveDocument { document_id }); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index b325bfd2ab..8af7b4e0f0 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -94,6 +94,10 @@ impl NodeGraphExecutor { execution_id } + pub fn clear_canvas_cache(&mut self) { + self.last_svg_canvas = None; + } + pub fn update_font_cache(&self, font_cache: FontCache) { self.runtime_io.send(GraphRuntimeRequest::FontCacheUpdate(font_cache)).expect("Failed to send font cache update"); } From 89ecbcccc22ec2d095af3900e55ae07c49814131 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 3 Mar 2026 03:41:02 -0800 Subject: [PATCH 2/2] Remove last_svg_canvas and do this logic in the frontend --- .../portfolio/portfolio_message_handler.rs | 4 ---- editor/src/node_graph_executor.rs | 16 ++-------------- frontend/src/components/panels/Document.svelte | 8 ++++++-- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 1b4c9540c4..e73eb4ef98 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1088,10 +1088,6 @@ impl MessageHandler> for Portfolio // Set the new active document ID self.active_document_id = Some(document_id); - // Clear the cached canvas frame so the frontend gets a fresh SVG with the canvas placeholder, - // since the previous document's DOM element was destroyed when the document view changed. - self.executor.clear_canvas_cache(); - responses.add(MenuBarMessage::SendLayout); responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(FrontendMessage::UpdateActiveDocument { document_id }); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 8af7b4e0f0..175540b05b 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -5,8 +5,7 @@ use graph_craft::document::value::{RenderOutput, TaggedValue}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; use graph_craft::wasm_application_io::EditorPreferences; -use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig}; -use graphene_std::application_io::{SurfaceFrame, TimingInformation}; +use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig, TimingInformation}; use graphene_std::raster::{CPU, Raster}; use graphene_std::renderer::{RenderMetadata, format_transform_matrix}; use graphene_std::text::FontCache; @@ -56,7 +55,6 @@ pub struct NodeGraphExecutor { futures: VecDeque<(u64, ExecutionContext)>, node_graph_hash: u64, previous_node_to_inspect: Option, - last_svg_canvas: Option, } #[derive(Debug, Clone)] @@ -79,7 +77,6 @@ impl NodeGraphExecutor { node_graph_hash: 0, current_execution_id: 0, previous_node_to_inspect: None, - last_svg_canvas: None, }; (node_runtime, node_executor) } @@ -94,10 +91,6 @@ impl NodeGraphExecutor { execution_id } - pub fn clear_canvas_cache(&mut self) { - self.last_svg_canvas = None; - } - pub fn update_font_cache(&self, font_cache: FontCache) { self.runtime_io.send(GraphRuntimeRequest::FontCacheUpdate(font_cache)).expect("Failed to send font cache update"); } @@ -388,19 +381,14 @@ impl NodeGraphExecutor { // Send to frontend responses.add(FrontendMessage::UpdateImageData { image_data }); responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); - self.last_svg_canvas = None; } - RenderOutputType::CanvasFrame(frame) => 'block: { - if self.last_svg_canvas == Some(frame) { - break 'block; - } + RenderOutputType::CanvasFrame(frame) => { let matrix = format_transform_matrix(frame.transform); let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") }; let svg = format!( r#"
"#, frame.resolution.x, frame.resolution.y, frame.surface_id.0, ); - self.last_svg_canvas = Some(frame); responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); } RenderOutputType::Texture { .. } => {} diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index d8453997f8..41ba94ee6c 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -198,9 +198,13 @@ const logicalWidth = parseFloat(foreignObject.getAttribute("width") || "0"); const logicalHeight = parseFloat(foreignObject.getAttribute("height") || "0"); - // Clone canvas for repeated instances (layers that appear multiple times) - // Viewport canvas is marked with data-is-viewport and should never be cloned + // Viewport canvas is marked with data-is-viewport and should never be cloned. + // If it's already mounted in the viewport, skip the DOM replacement since it's already showing the rendered content. + // We check `canvas.isConnected` to ensure it's in the live DOM, not a detached tree from a destroyed component. const isViewport = placeholder.hasAttribute("data-is-viewport"); + if (isViewport && canvas.isConnected && canvas.parentElement?.closest("[data-viewport]")) return; + + // Clone canvas for repeated instances (layers that appear multiple times) if (!isViewport && canvas.parentElement) { const newCanvas = window.document.createElement("canvas"); const context = newCanvas.getContext("2d");