Skip to content

Commit dd9a780

Browse files
committed
fix(ui): defer exact Monaco paste admission
1 parent 71447c4 commit dd9a780

5 files changed

Lines changed: 47 additions & 11 deletions

File tree

apps/sim/app/_shell/paste-admission-guard.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ describe('PasteAdmissionGuard', () => {
100100
expect(dispatchPaste(editable, 'a').defaultPrevented).toBe(false)
101101
})
102102

103+
it('defers text admission to an editor that projects its exact paste result', () => {
104+
const editor = document.createElement('div')
105+
editor.setAttribute('contenteditable', 'true')
106+
editor.dataset.pasteMaxBytes = '4'
107+
editor.dataset.pasteProjectsTextResult = 'true'
108+
host.appendChild(editor)
109+
110+
const targetHandler = vi.fn()
111+
editor.addEventListener('paste', targetHandler)
112+
expect(dispatchPaste(editor, '12345').defaultPrevented).toBe(false)
113+
expect(targetHandler).toHaveBeenCalledOnce()
114+
})
115+
103116
it('lets a prompt consume a compact Sim selection reference before its large plain text', () => {
104117
const input = document.createElement('textarea')
105118
input.dataset.pasteMaxBytes = '4'

apps/sim/app/_shell/paste-admission-guard.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,22 @@ export function PasteAdmissionGuard() {
4949
if (handlesImageFiles && clipboardHasImageFile(event.clipboardData)) return
5050

5151
const text = event.clipboardData?.getData('text/plain') ?? ''
52+
const projectsTextResult = event.target.closest('[data-paste-projects-text-result="true"]')
5253
const policyElement = event.target.closest('[data-paste-max-bytes]')
5354
const maxPastedBytes =
5455
finitePositiveAttribute(policyElement, 'data-paste-max-bytes') ?? PASTE_LIMITS.DEFAULT_BYTES
5556
const maxPastedCharacters = finitePositiveAttribute(
5657
policyElement,
5758
'data-paste-max-characters'
5859
)
59-
const textAdmission = text
60-
? assessTextPaste({
61-
pastedText: text,
62-
maxPastedBytes,
63-
maxPastedCharacters,
64-
})
65-
: null
60+
const textAdmission =
61+
text && !projectsTextResult
62+
? assessTextPaste({
63+
pastedText: text,
64+
maxPastedBytes,
65+
maxPastedCharacters,
66+
})
67+
: null
6668
const htmlPolicyElement = event.target.closest('[data-paste-max-html-bytes]')
6769
const maxPastedHtmlBytes = finitePositiveAttribute(
6870
htmlPolicyElement,

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor-paste.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ describe('assessTextEditorPaste', () => {
6060
).toMatchObject({ accepted: true, resultBytes: 10 })
6161
})
6262

63+
it('admits a distributed result when only removed line separators exceed the boundary', () => {
64+
expect(
65+
assessTextEditorPaste(
66+
{
67+
pastedText: 'a\nb\nc\nd\ne\nf',
68+
currentText: '1234',
69+
selections: [
70+
{ start: 0, end: 0 },
71+
{ start: 1, end: 1 },
72+
{ start: 2, end: 2 },
73+
{ start: 3, end: 3 },
74+
{ start: 4, end: 4 },
75+
{ start: 4, end: 4 },
76+
],
77+
},
78+
10
79+
)
80+
).toMatchObject({ accepted: true, resultBytes: 10 })
81+
})
82+
6383
it('projects the full clipboard at every cursor when Monaco spread mode is disabled', () => {
6484
expect(
6585
assessTextEditorPaste(

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor-paste.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function assessTextEditorPaste(
9090
: input.pastedText.length * selections.length
9191
const resultCharacters = input.currentText.length - replacedCharacters + insertedCharacters
9292

93-
if (input.pastedText.length > maxBytes) {
93+
if (!distributedRanges && input.pastedText.length > maxBytes) {
9494
return {
9595
accepted: false,
9696
reason: 'pasted-bytes',
@@ -103,8 +103,8 @@ export function assessTextEditorPaste(
103103
return { accepted: true, resultCharacters }
104104
}
105105

106-
const pastedBytes = utf8ByteLength(input.pastedText, maxBytes)
107-
if (pastedBytes > maxBytes) {
106+
const pastedBytes = distributedRanges ? undefined : utf8ByteLength(input.pastedText, maxBytes)
107+
if (pastedBytes !== undefined && pastedBytes > maxBytes) {
108108
return { accepted: false, reason: 'pasted-bytes', actual: pastedBytes, limit: maxBytes }
109109
}
110110

@@ -114,7 +114,7 @@ export function assessTextEditorPaste(
114114
total + utf8ByteLengthRange(input.pastedText, range.start, range.end, maxBytes - total),
115115
0
116116
)
117-
: pastedBytes * selections.length
117+
: (pastedBytes ?? 0) * selections.length
118118
if (insertedBytes > maxBytes) {
119119
return { accepted: false, reason: 'result-bytes', actual: insertedBytes, limit: maxBytes }
120120
}

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/text-editor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ export const TextEditor = memo(function TextEditor({
683683
{showEditor && (
684684
<div
685685
data-paste-max-bytes={PASTE_LIMITS.TEXT_EDITOR_BYTES}
686+
data-paste-projects-text-result='true'
686687
onPasteCapture={handleEditorPasteCapture}
687688
style={showPreviewPane ? { width: `${splitPct}%`, flexShrink: 0 } : undefined}
688689
className={cn(

0 commit comments

Comments
 (0)