Clipboard: Cross-application copy-paste support - #4499
Conversation
There was a problem hiding this comment.
5 issues found across 8 files
Confidence score: 2/5
- In
editor/src/node_graph_executor/runtime.rs, selecting an artboard produces an empty SVG because its monitor output is ignored, risking incorrect or missing clipboard/rendered output; render artboards while preserving location, dimensions, and clipping. - In
editor/src/node_graph_executor/runtime.rs,run()can silently dropCopySvgTextClipboardwhen it is batched with anExecutionRequest, so clipboard copying becomes unreliable; process both requests without returning before the clipboard operation. - The layer-copy path in
editor/src/messages/clipboard/clipboard_message_handler.rsnow routes through a runtime-generated frontend message thatEditorTestUtils::handle_messagediscards, breaking the existing clipboard-copy test flow; preserve or explicitly handle the clipboard write message in tests. - In
frontend/src/managers/clipboard.ts, callingClipboardItem.supports()without checking that the method exists breaks browsers such as Safari before 18.4; guard the capability check and keep the fallback path. Separately,editor/src/node_graph_executor.rscan panic on clipboard-send failure via.expect(...); handle the error with logging instead.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="editor/src/node_graph_executor.rs">
<violation number="1" location="editor/src/node_graph_executor.rs:820">
P3: `copy_svg_clipboard` panics the whole application on send failure via `.expect(...)`. Match the codebase's preferred non-panicking style for this new public entry point: handle the send result with a log (e.g., `if let Err(e) = self.runtime_io.send(...) { log::error!(...) }`) instead of asserting, so a runtime-IO failure doesn't crash the editor.</violation>
</file>
<file name="editor/src/node_graph_executor/runtime.rs">
<violation number="1" location="editor/src/node_graph_executor/runtime.rs:203">
P1: When a `CopySvgTextClipboard` request is batched with an `ExecutionRequest` in the same `run()` pass, the copy is silently dropped. The `ExecutionRequest` arm ends with `return texture;`, and `svg_clipboard` is ordered after `execution` in the requests array, so the early return exits before the copy is processed. Since `run()` drains all queued requests each frame, a copy issued while a viewport render is queued loses the clipboard write with no response sent. Process `svg_clipboard` before the execution arm's early return (e.g., place it earlier in the array), or handle it before the `return`.</violation>
<violation number="2" location="editor/src/node_graph_executor/runtime.rs:351">
P1: When the selection contains an artboard, this branch ignores its `List<Artboard>` monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.</violation>
</file>
<file name="editor/src/messages/clipboard/clipboard_message_handler.rs">
<violation number="1" location="editor/src/messages/clipboard/clipboard_message_handler.rs:93">
P2: This changes layer copy from a synchronous `TriggerClipboardWrite` into a runtime request whose generated frontend message is discarded by `EditorTestUtils::handle_message`. As a result, the existing clipboard copy tests fail at their `.expect`; propagate the asynchronous clipboard response through the test/runtime API or preserve a directly observable write result.</violation>
</file>
<file name="frontend/src/managers/clipboard.ts">
<violation number="1" location="frontend/src/managers/clipboard.ts:29">
P2: On browsers where the `ClipboardItem` interface exists but the static `supports()` method is unavailable (e.g. Safari before 18.4, which shipped `ClipboardItem` in 13.1 but `supports()` only in 18.4), `ClipboardItem.supports(...)` throws a TypeError before the `else` fallback runs, so even the `writeText` fallback never executes and the copy fails entirely. Guard the feature detection (e.g., `ClipboardItem.supports?.(...)` or wrap in try/catch, as the linked MDN example does) so non-supporting browsers fall back to `writeText`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return texture; | ||
| } | ||
| GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => { | ||
| let mut combined_graphics = List::<Graphic>::new(); |
There was a problem hiding this comment.
P1: When the selection contains an artboard, this branch ignores its List<Artboard> monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 351:
<comment>When the selection contains an artboard, this branch ignores its `List<Artboard>` monitor output and sends an empty SVG. Render artboard outputs as SVG too, preserving their location, dimensions, and clipping.</comment>
<file context>
@@ -340,6 +347,59 @@ impl NodeRuntime {
return texture;
}
+ GraphRuntimeRequest::CopySvgTextClipboard(text_string_clipboard, selected_node_ids) => {
+ let mut combined_graphics = List::<Graphic>::new();
+
+ for monitor_node_path in &self.monitor_nodes {
</file context>
| } | ||
|
|
||
| let requests = [preferences, graph, eyedropper, execution].into_iter().flatten(); | ||
| let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten(); |
There was a problem hiding this comment.
P1: When a CopySvgTextClipboard request is batched with an ExecutionRequest in the same run() pass, the copy is silently dropped. The ExecutionRequest arm ends with return texture;, and svg_clipboard is ordered after execution in the requests array, so the early return exits before the copy is processed. Since run() drains all queued requests each frame, a copy issued while a viewport render is queued loses the clipboard write with no response sent. Process svg_clipboard before the execution arm's early return (e.g., place it earlier in the array), or handle it before the return.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor/runtime.rs, line 203:
<comment>When a `CopySvgTextClipboard` request is batched with an `ExecutionRequest` in the same `run()` pass, the copy is silently dropped. The `ExecutionRequest` arm ends with `return texture;`, and `svg_clipboard` is ordered after `execution` in the requests array, so the early return exits before the copy is processed. Since `run()` drains all queued requests each frame, a copy issued while a viewport render is queued loses the clipboard write with no response sent. Process `svg_clipboard` before the execution arm's early return (e.g., place it earlier in the array), or handle it before the `return`.</comment>
<file context>
@@ -193,7 +200,7 @@ impl NodeRuntime {
}
- let requests = [preferences, graph, eyedropper, execution].into_iter().flatten();
+ let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten();
for request in requests {
</file context>
| let requests = [preferences, graph, eyedropper, execution, svg_clipboard].into_iter().flatten(); | |
| let requests = [preferences, graph, svg_clipboard, eyedropper, execution].into_iter().flatten(); |
| responses.add(FrontendMessage::TriggerClipboardWrite { content: text }); | ||
| ClipboardContent::Graphite(graphite) => { | ||
| let graphite_json = format!("{CLIPBOARD_PREFIX}{graphite}"); | ||
| responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json }); |
There was a problem hiding this comment.
P2: This changes layer copy from a synchronous TriggerClipboardWrite into a runtime request whose generated frontend message is discarded by EditorTestUtils::handle_message. As a result, the existing clipboard copy tests fail at their .expect; propagate the asynchronous clipboard response through the test/runtime API or preserve a directly observable write result.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/messages/clipboard/clipboard_message_handler.rs, line 93:
<comment>This changes layer copy from a synchronous `TriggerClipboardWrite` into a runtime request whose generated frontend message is discarded by `EditorTestUtils::handle_message`. As a result, the existing clipboard copy tests fail at their `.expect`; propagate the asynchronous clipboard response through the test/runtime API or preserve a directly observable write result.</comment>
<file context>
@@ -80,19 +80,22 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
- responses.add(FrontendMessage::TriggerClipboardWrite { content: text });
+ ClipboardContent::Graphite(graphite) => {
+ let graphite_json = format!("{CLIPBOARD_PREFIX}{graphite}");
+ responses.add(PortfolioMessage::RequestSvgTextCopy { graphite_json });
+ }
+ ClipboardContent::Text(text) => {
</file context>
|
|
||
| subscriptions.subscribeFrontendMessage("TriggerClipboardSvgWrite", (data) => { | ||
| // Adopted from https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem#browser_compatibility | ||
| if (ClipboardItem.supports("image/svg+xml")) { |
There was a problem hiding this comment.
P2: On browsers where the ClipboardItem interface exists but the static supports() method is unavailable (e.g. Safari before 18.4, which shipped ClipboardItem in 13.1 but supports() only in 18.4), ClipboardItem.supports(...) throws a TypeError before the else fallback runs, so even the writeText fallback never executes and the copy fails entirely. Guard the feature detection (e.g., ClipboardItem.supports?.(...) or wrap in try/catch, as the linked MDN example does) so non-supporting browsers fall back to writeText.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/managers/clipboard.ts, line 29:
<comment>On browsers where the `ClipboardItem` interface exists but the static `supports()` method is unavailable (e.g. Safari before 18.4, which shipped `ClipboardItem` in 13.1 but `supports()` only in 18.4), `ClipboardItem.supports(...)` throws a TypeError before the `else` fallback runs, so even the `writeText` fallback never executes and the copy fails entirely. Guard the feature detection (e.g., `ClipboardItem.supports?.(...)` or wrap in try/catch, as the linked MDN example does) so non-supporting browsers fall back to `writeText`.</comment>
<file context>
@@ -23,13 +23,28 @@ export function createClipboardManager(subscriptions: SubscriptionsRouter, edito
+
+ subscriptions.subscribeFrontendMessage("TriggerClipboardSvgWrite", (data) => {
+ // Adopted from https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem#browser_compatibility
+ if (ClipboardItem.supports("image/svg+xml")) {
+ navigator.clipboard?.write?.([
+ new ClipboardItem({
</file context>
| pub fn copy_svg_clipboard(&self, graphite_json: String, selected_nodes: Vec<NodeId>) { | ||
| self.runtime_io | ||
| .send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json, selected_nodes)) | ||
| .expect("Failed to send runtime request"); |
There was a problem hiding this comment.
P3: copy_svg_clipboard panics the whole application on send failure via .expect(...). Match the codebase's preferred non-panicking style for this new public entry point: handle the send result with a log (e.g., if let Err(e) = self.runtime_io.send(...) { log::error!(...) }) instead of asserting, so a runtime-IO failure doesn't crash the editor.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At editor/src/node_graph_executor.rs, line 820:
<comment>`copy_svg_clipboard` panics the whole application on send failure via `.expect(...)`. Match the codebase's preferred non-panicking style for this new public entry point: handle the send result with a log (e.g., `if let Err(e) = self.runtime_io.send(...) { log::error!(...) }`) instead of asserting, so a runtime-IO failure doesn't crash the editor.</comment>
<file context>
@@ -812,6 +816,12 @@ impl NodeGraphExecutor {
Ok(())
}
+
+ pub fn copy_svg_clipboard(&self, graphite_json: String, selected_nodes: Vec<NodeId>) {
+ self.runtime_io
+ .send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json, selected_nodes))
</file context>
| pub fn copy_svg_clipboard(&self, graphite_json: String, selected_nodes: Vec<NodeId>) { | |
| self.runtime_io | |
| .send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json, selected_nodes)) | |
| .expect("Failed to send runtime request"); | |
| pub fn copy_svg_clipboard(&self, graphite_json: String, selected_nodes: Vec<NodeId>) { | |
| if let Err(error) = self.runtime_io.send(GraphRuntimeRequest::CopySvgTextClipboard(graphite_json, selected_nodes)) { | |
| log::error!("Failed to send runtime request: {error:?}"); | |
| } | |
| } |
Description
This PR aims to resolve #2373. Solution's approach has been thoroughly discussed in discord's development channel. The PR will fix this issue by introducing copy of both text and svg+xml mime types when copying a selection to the clipboard. Essentially making it possible for other apps to take the svg representation while graphite picks up the internal json representation for copy pasting.
Notes
image/svg+xmlis not supported by firefox and safari Stable so that is handled.Following example showcase the same copy paste not working across apps (
kritaandinkscapeshown as the other apps) in current build vs it working in this branch while still being compatible across graphite tabs.Before(Only across graphite tabs, not across apps):
editor_before.mp4
After(Across tabs and other apps):
editor_after.mp4