diff --git a/apps/sim/app/_shell/paste-admission-guard.test.tsx b/apps/sim/app/_shell/paste-admission-guard.test.tsx index d10ad45b2c0..50b3d9dc0dc 100644 --- a/apps/sim/app/_shell/paste-admission-guard.test.tsx +++ b/apps/sim/app/_shell/paste-admission-guard.test.tsx @@ -17,6 +17,18 @@ import { PasteAdmissionGuard } from '@/app/_shell/paste-admission-guard' let host: HTMLDivElement let root: Root +const selectionContext = { + kind: 'table_selection', + tableId: 'table-1', + tableName: 'Large table', + rowIds: ['row-1'], + label: 'Large table (1 row)', +} + +function selectionPayload(sourceWorkspaceId = 'ws-1'): string { + return JSON.stringify({ version: 1, sourceWorkspaceId, context: selectionContext }) +} + function dispatchPaste( target: Element, text: string, @@ -116,32 +128,33 @@ describe('PasteAdmissionGuard', () => { it('lets a prompt consume a compact Sim selection reference before its large plain text', () => { const input = document.createElement('textarea') input.dataset.pasteMaxBytes = '4' - input.dataset.pasteSelectionContext = 'reference' + input.dataset.pasteSelectionContext = 'ws-1' + host.appendChild(input) + + expect( + dispatchPaste(input, '12345', { selectionContext: selectionPayload() }).defaultPrevented + ).toBe(false) + }) + + it('still bounds a cross-workspace selection plain-text representation', () => { + const input = document.createElement('textarea') + input.dataset.pasteMaxBytes = '4' + input.dataset.pasteSelectionContext = 'ws-2' host.appendChild(input) - const selectionContext = JSON.stringify({ - kind: 'table_selection', - tableId: 'table-1', - tableName: 'Large table', - rowIds: ['row-1'], - label: 'Large table (1 row)', - }) - expect(dispatchPaste(input, '12345', { selectionContext }).defaultPrevented).toBe(false) + expect( + dispatchPaste(input, '12345', { selectionContext: selectionPayload() }).defaultPrevented + ).toBe(true) }) it('still bounds a Sim selection plain-text representation outside the prompt', () => { const input = document.createElement('textarea') input.dataset.pasteMaxBytes = '4' host.appendChild(input) - const selectionContext = JSON.stringify({ - kind: 'table_selection', - tableId: 'table-1', - tableName: 'Large table', - rowIds: ['row-1'], - label: 'Large table (1 row)', - }) - expect(dispatchPaste(input, '12345', { selectionContext }).defaultPrevented).toBe(true) + expect( + dispatchPaste(input, '12345', { selectionContext: selectionPayload() }).defaultPrevented + ).toBe(true) }) it('bounds rich HTML separately from its smaller plain-text representation', () => { diff --git a/apps/sim/app/_shell/paste-admission-guard.tsx b/apps/sim/app/_shell/paste-admission-guard.tsx index 1a12cb1f3a7..2ee15ab9c06 100644 --- a/apps/sim/app/_shell/paste-admission-guard.tsx +++ b/apps/sim/app/_shell/paste-admission-guard.tsx @@ -43,7 +43,15 @@ export function PasteAdmissionGuard() { } const acceptsSelectionContext = event.target.closest('[data-paste-selection-context]') - if (acceptsSelectionContext && readSelectionContextFromClipboard(event.clipboardData)) return + const destinationWorkspaceId = acceptsSelectionContext?.getAttribute( + 'data-paste-selection-context' + ) + if ( + destinationWorkspaceId && + readSelectionContextFromClipboard(event.clipboardData, destinationWorkspaceId) + ) { + return + } const handlesImageFiles = event.target.closest('[data-paste-handles-images="true"]') if (handlesImageFiles && clipboardHasImageFile(event.clipboardData)) return diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx index e9c171c59fb..daf7eb5cb29 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx @@ -1213,7 +1213,7 @@ export function LoadedRichMarkdownEditor({ if (context) addToChat(context) } - useSelectionCopyBridge(containerRef, buildSelectionContext) + useSelectionCopyBridge(containerRef, buildSelectionContext, workspaceId) // Show the read-only placeholder (the already-fetched markdown) whenever a collaborative doc has not yet // seeded — including during an agent stream that begins before the seed lands. Streamed diffs are held diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx index 8a4feedd697..883bf4bc6f4 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx @@ -493,7 +493,7 @@ export const TextEditor = memo(function TextEditor({ // Enable once content has loaded — the container (and Monaco) only mount after // the `isContentLoading` early return below, so the bridge must (re-)attach then. - useSelectionCopyBridge(containerRef, buildSelectionContext, !isContentLoading) + useSelectionCopyBridge(containerRef, buildSelectionContext, workspaceId, !isContentLoading) useEffect(() => { if (lastEditorValueRef.current === content) return diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/use-selection-copy-bridge.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/use-selection-copy-bridge.test.tsx index b457be62f2a..f681e8b7cdc 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/use-selection-copy-bridge.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/use-selection-copy-bridge.test.tsx @@ -26,7 +26,7 @@ let buildContext: ReturnType * textarea, and its find widget is a real input nested in the same container. */ function Host() { - useSelectionCopyBridge(containerRef, buildContext as () => ChatContext | null) + useSelectionCopyBridge(containerRef, buildContext as () => ChatContext | null, 'ws-1') return (