Skip to content

fix: Copy + Paste Across Tabs - #2705

Open
camielvs wants to merge 1 commit into
masterfrom
fix-copy-paste-again
Open

fix: Copy + Paste Across Tabs#2705
camielvs wants to merge 1 commit into
masterfrom
fix-copy-paste-again

Conversation

@camielvs

@camielvs camielvs commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Description

Copying nodes in one tab and pasting them in another silently did nothing. Cross-instance transfer was already the intent — copy wrote a tangle-pipeline-nodes JSON envelope to the system clipboard and paste read it back — but the read went through navigator.clipboard.readText(), which is gated behind the clipboard-read permission in Chrome and is not freely available to pages in Firefox. Every failure was swallowed by a bare catch {}, and paste then fell back to the in-memory store, which is empty in a fresh tab. Result: nothing pasted, no feedback.

Two changes:

1. Paste reads from the native paste event. readEnvelopeFromPasteEvent pulls the envelope out of ClipboardEvent.clipboardData, which needs no permission and works in every browser without a prompt. useClipboardShortcuts now listens for paste on window, ignores editable targets so text still pastes into inputs and Monaco normally, and pastes at the canvas centre as before.

The Cmd+V keydown shortcut stays registered so it remains discoverable in the shortcut list, but its action returns false — the mechanism ShortcutDefinition already documents for "let the native event propagate" — so the listener skips preventDefault and the browser goes on to fire paste. Previously that preventDefault suppressed the paste event entirely, which is what made clipboardData unreachable in the first place.

ClipboardStore.paste treats a paste-event read as authoritative and skips the async read when it has one, so the permission prompt is gone on the normal path. It still falls back to readText() if an event arrives without clipboardData.

2. Clipboard failures surface. writeToSystemClipboard now rejects instead of swallowing, and paste returns a PasteOutcome of pasted / nothing-to-paste / clipboard-unavailable. All four copy call sites (editor shortcut and toolbar, run view shortcut and toolbar) and the paste handler report failures via toast.

This also revives a catch in DashboardComponentsV2View.handleCopyToPipeline that could never fire before, because writeToSystemClipboard swallowed the error internally — "Copy to pipeline" reported success unconditionally, even when the clipboard write was refused.

Two deliberate omissions:

  • Pasting unrelated clipboard content over the canvas stays silent rather than toasting. Cmd+V with a text clipboard shouldn't nag.
  • With clipboardData doing the work, the read-failure toast is a safety net rather than a common path — it fires only when an event arrives without clipboardData and the async read is also refused. The copy-failure toast is the reachable one.

Not in scope

Ctrl+C/Ctrl+V are still unbound. The clipboard shortcuts register against CMDALT, which keys.ts produces only from Meta or Alt — Control maps to a separate CTRL constant and matchesPressed requires an exact set match. On macOS that is fine (⌘C/⌘V). On Windows/Linux only Alt+C/Alt+V work, while ShortcutBadge renders CMDALT as the literal string "Ctrl" — so the UI advertises a binding that does not exist. Worth a separate fix; it is a display/binding mismatch rather than a clipboard bug.

Related Issue and Pull requests

None.

Type of Change

  • Bug fix
  • New feature
  • Improvement
  • Cleanup/Refactor
  • Breaking change
  • Documentation update

Checklist

  • I have tested this does not break current pipelines / runs functionality
  • I have tested the changes on staging

Unit suite: 2485 passing across 232 files. Typecheck, lint and format clean. The Playwright E2E suite was not run — it has no coverage of copy/paste.

Screenshots (if applicable)

No visual change beyond the two error toasts, whose copy is in clipboardMessages.ts:

  • Couldn't copy to the clipboard. Check browser permissions and try again.
  • Couldn't read the clipboard. Check browser permissions and try again.

Test Instructions

Cross-tab paste (the actual bug):

  1. Open the V2 editor on a pipeline with at least one task.
  2. Select one or more nodes and press ⌘C.
  3. Open a second tab (or a second instance) on a different pipeline in the V2 editor.
  4. Click the canvas and press ⌘V. The nodes, and any connections purely between them, should appear centred in the viewport. No clipboard permission prompt should appear.
  5. Press ⌘V again — each paste cascades by 50px rather than stacking.

Regressions to check:

  1. Paste text into the component search field, a task argument, and the Monaco editor — all should behave natively, with no node created.
  2. ⌘C with a text selection on the page should still copy that text rather than the selected nodes.
  3. Same-tab copy/paste and ⌘D duplicate should be unchanged.

Failure feedback (needs devtools):

  1. In the console, run:
    Object.defineProperty(navigator, "clipboard", {
      value: {
        writeText: () => Promise.reject(new Error("NotAllowed")),
        readText: () => Promise.reject(new Error("NotAllowed")),
      },
      configurable: true,
    });
  2. Select a node and press ⌘C → copy-failure toast.
  3. Reload, re-apply the override, then run
    document.body.dispatchEvent(new ClipboardEvent("paste", {bubbles: true, cancelable: true}))
    → read-failure toast.

Additional Comments

Verified by driving the real app in Chromium against the dev server: an envelope placed on the system clipboard pasted into the V2 canvas as a task node, with the native paste event reaching the listener (clipboardData present, target BODY); pasting into the component search field still inserted text with no node created; and both toasts appeared under a forced-failure clipboard.

New tests: 11 for the envelope read/write paths (clipboardEnvelope.test.ts) and 8 for the store (clipboardStore.test.ts), including that an envelope from another tab short-circuits the async read, that a denied read yields clipboard-unavailable, and that the paste offset does not advance when nothing is pasted.

clipboardStore.test.ts mocks @/routes/v2/pages/Editor/nodes — the real node registry transitively imports the router, so the store cannot be unit-tested against it.

@camielvs
camielvs requested a review from a team as a code owner September 5, 2026 01:19
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: fix-copy-paste-again/237dc69

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant