Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Feb 1, 2026

Automatically convert large pasted text (>5,000 characters) into a file attachment in the chat input to improve UX and prevent UI clogging. Includes collision handling and improved validation feedback via toast notifications.


PR created automatically by Jules for task 5580601503969984577 started by @ngoiyaeric

Summary by CodeRabbit

  • Bug Fixes

    • Replaced intrusive browser alerts with non-intrusive toast notifications for file size validation (10MB limit).
  • New Features

    • Large text pastes (over 500 characters) are automatically converted into attachable text files and clear the input; pastes are ignored if an attachment already exists and show a toast error.
  • Tests

    • Added end-to-end tests covering paste-to-file conversion and related error handling.
  • Chores

    • Updated toast notification implementation.

✏️ Tip: You can customize this high-level summary in your review settings.

This change implements a UX improvement where pasted text exceeding 5,000 characters is automatically converted into a `.txt` file attachment named `pasted-text.txt`.

Key changes:
- Added `onPaste` handler to the chat input `Textarea`.
- Implemented character threshold (5,000) for auto-conversion.
- Added collision handling: if a file is already attached, the large paste is prevented and a toast notification is shown.
- Updated file size validation to use `sonner` toasts instead of browser alerts for consistency.
- Ensured `e.preventDefault()` is called for large pastes to prevent UI clogging.
- Verified functionality with targeted Playwright tests and frontend screenshots.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link

vercel bot commented Feb 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
qcx Ready Ready Preview, Comment Feb 1, 2026 8:19pm

@charliecreates charliecreates bot requested a review from CharlieHelps February 1, 2026 19:30
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Walkthrough

Replaces browser alerts with sonner toasts, adds paste-to-file behavior: pasted text over 500 chars is converted into a pasted-text.txt File (10MB paste/file limit) when no attachment exists; pastes are blocked and show a toast if an attachment already exists. Adds Playwright tests for paste behavior.

Changes

Cohort / File(s) Summary
Chat panel (paste & toast)
components/chat-panel.tsx
Imported toast from sonner; replaced alert with toast.error for 10MB validation. Added handlePaste to convert pasted text >500 chars into a pasted-text.txt File when no attachment exists, enforce 10MB limit, prevent default paste when attachment exists, clear input, and wire via Textarea onPaste.
Header toast import
components/header-search-button.tsx
Replaced react-toastify import with sonner for toast API (usage unchanged).
Tests
tests/paste.spec.ts
Added Playwright tests for paste-to-file conversion: large paste -> creates attachment and clears input; small paste -> no attachment; paste with existing attachment -> shows error message.
Manifest
package.json
Small change related to toast package swap (import/source change reflected).

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Textarea as Textarea (ChatPanel)
    participant ChatPanel as ChatPanel State
    participant FileAPI as Browser File API
    participant Toast as sonner.toast

    User->>Textarea: Paste text
    Textarea->>ChatPanel: onPaste event (clipboardData)
    ChatPanel->>ChatPanel: read clipboard text, check length (>500?) and check existing attachment
    alt no attachment & text > 500 chars
        ChatPanel->>FileAPI: create File("pasted-text.txt") (ensure <=10MB)
        FileAPI-->>ChatPanel: File object
        ChatPanel->>ChatPanel: set attachment state, clear input
    else attachment exists
        ChatPanel->>Toast: toast.error("Please remove the current attachment")
        ChatPanel-->>Textarea: prevent default paste
    else text <= 500 chars
        ChatPanel-->>Textarea: allow default paste (no file created)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Review effort 3/5

Poem

🐇 I nibble words from clipboard night,
I stitch them into files so light.
No startling bells, just gentle toast,
A tiny file for what I boast.
Hop on—your paste is safe with me! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: auto-converting large pasted text into file attachments.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/pasted-text-to-file-conversion-5580601503969984577

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core UX behavior looks correct, but the new paste-to-file path currently bypasses existing file-size limits, allowing very large clipboard content to be converted into an attachment. Additionally, the paste handler should avoid interfering with non-text clipboard payloads and should not rely on an inline magic number for the threshold. Addressing these will improve robustness and prevent memory/performance issues.

Summary of changes

Summary

This PR improves chat input UX by converting large pasted text into a file attachment and modernizing validation messaging.

Key updates

  • Added sonner toast notifications via import { toast } from 'sonner'.
  • Replaced the 10MB attachment validation alert(...) with toast.error(...).
  • Introduced an onPaste handler on the chat Textarea to:
    • Detect large clipboard text (> 5000 chars)
    • preventDefault() to avoid inserting huge text into the textarea
    • Create a File named pasted-text.txt and attach it when no attachment exists
    • Show an error toast when an attachment already exists (collision handling)
  • Removed server.log from the repository.

Comment on lines 80 to 96
const handlePaste = (e: React.ClipboardEvent) => {
const pastedText = e.clipboardData.getData('text')
if (pastedText.length > 5000) {
e.preventDefault()
if (selectedFile) {
toast.error(
'Please remove the current attachment to convert large paste to file'
)
return
}
const file = new File([pastedText], 'pasted-text.txt', {
type: 'text/plain'
})
setSelectedFile(file)
setInput('')
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The paste threshold (5000) is a magic number here. It should be extracted into a named constant (ideally colocated with related upload/validation constants) so it’s easier to discover, adjust, and test consistently across the codebase.

Suggestion

Extract the threshold into a constant (and consider reusing it in tests).

const LARGE_PASTE_CHAR_THRESHOLD = 5000

const handlePaste = (e: React.ClipboardEvent) => {
  const pastedText = e.clipboardData.getData('text')
  if (pastedText.length > LARGE_PASTE_CHAR_THRESHOLD) {
    // ...
  }
}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines 80 to 96
const handlePaste = (e: React.ClipboardEvent) => {
const pastedText = e.clipboardData.getData('text')
if (pastedText.length > 5000) {
e.preventDefault()
if (selectedFile) {
toast.error(
'Please remove the current attachment to convert large paste to file'
)
return
}
const file = new File([pastedText], 'pasted-text.txt', {
type: 'text/plain'
})
setSelectedFile(file)
setInput('')
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handlePaste always reads text from the clipboard. In some browsers/contexts, a paste may primarily contain files/HTML, and forcing getData('text') + large-text prevention could accidentally interfere with non-text pastes. Consider guarding so this logic only runs when there is meaningful plain-text content and otherwise lets the default paste behavior proceed.

Suggestion

Add conservative guards so you only intercept large plain-text pastes and do not block other clipboard payloads.

const handlePaste = (e: React.ClipboardEvent) => {
  const text = e.clipboardData.getData('text/plain')
  if (!text) return

  if (text.length > LARGE_PASTE_CHAR_THRESHOLD) {
    e.preventDefault()
    if (selectedFile) {
      toast.error('Please remove the current attachment to convert large paste to file')
      return
    }

    setSelectedFile(new File([text], 'pasted-text.txt', { type: 'text/plain' }))
    setInput('')
  }
}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines 80 to 96
const handlePaste = (e: React.ClipboardEvent) => {
const pastedText = e.clipboardData.getData('text')
if (pastedText.length > 5000) {
e.preventDefault()
if (selectedFile) {
toast.error(
'Please remove the current attachment to convert large paste to file'
)
return
}
const file = new File([pastedText], 'pasted-text.txt', {
type: 'text/plain'
})
setSelectedFile(file)
setInput('')
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converting pasted text into a File bypasses the existing 10MB check in handleFileChange. A user could paste extremely large text and end up with a huge in-memory File (and later potentially a failed upload/server rejection). You should apply the same size limit (and ideally share the constant) when creating the file from pasted content.

Suggestion

Reuse the same max-size validation for pasted-text files before calling setSelectedFile.

const MAX_FILE_BYTES = 10 * 1024 * 1024

// ... inside large paste branch
const file = new File([text], 'pasted-text.txt', { type: 'text/plain' })
if (file.size > MAX_FILE_BYTES) {
  toast.error('File size must be less than 10MB')
  return
}
setSelectedFile(file)
setInput('')

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

@charliecreates charliecreates bot removed the request for review from CharlieHelps February 1, 2026 19:32
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@components/chat-panel.tsx`:
- Around line 90-93: The pasted-text File creation bypasses the same 10MB
validation used in handleFileChange; before calling setSelectedFile(file) in the
paste handling code that constructs File([pastedText], 'pasted-text.txt', ...),
check the byte size of pastedText (or file.size) against the existing maxSize
used by handleFileChange (10 * 1024 * 1024) and reject/notify the user the same
way handleFileChange does if it exceeds the limit so pasted files follow the
same size validation flow.
- Around line 80-96: Add a success toast when large pasted text is converted to
a File: inside handlePaste, after creating the File and calling
setSelectedFile(file) and setInput(''), call toast.success with a brief message
like "Pasted text attached as file" so the user sees the conversion. Also wrap
handlePaste in React.useCallback (same pattern as debouncedGetSuggestions) and
include its dependencies (selectedFile, setSelectedFile, setInput, toast) to
avoid unnecessary re-renders and keep prop stability for any memoized Textarea
component.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 250283e and 9a54a27.

⛔ Files ignored due to path filters (1)
  • server.log is excluded by !**/*.log
📒 Files selected for processing (1)
  • components/chat-panel.tsx
🔇 Additional comments (3)
components/chat-panel.tsx (3)

6-6: LGTM!

The toast import from sonner is correctly added to support the new toast-based feedback.


72-75: LGTM!

Replacing the browser alert with toast.error is a good UX improvement — it's non-blocking and consistent with the new paste handling feedback.


271-271: LGTM!

The onPaste handler is correctly wired to the Textarea component.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 80 to 96
const handlePaste = (e: React.ClipboardEvent) => {
const pastedText = e.clipboardData.getData('text')
if (pastedText.length > 5000) {
e.preventDefault()
if (selectedFile) {
toast.error(
'Please remove the current attachment to convert large paste to file'
)
return
}
const file = new File([pastedText], 'pasted-text.txt', {
type: 'text/plain'
})
setSelectedFile(file)
setInput('')
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding a success toast when paste is converted to file.

When large text is converted to a file, the user sees their input cleared and a file attachment appear. Adding a success toast would make this behavior more discoverable and less confusing.

Additionally, consider wrapping handlePaste in useCallback for consistency with debouncedGetSuggestions and to avoid unnecessary re-renders if the Textarea memoizes props.

♻️ Proposed enhancement
-  const handlePaste = (e: React.ClipboardEvent) => {
+  const handlePaste = useCallback((e: React.ClipboardEvent) => {
     const pastedText = e.clipboardData.getData('text')
     if (pastedText.length > 5000) {
       e.preventDefault()
       if (selectedFile) {
         toast.error(
           'Please remove the current attachment to convert large paste to file'
         )
         return
       }
       const file = new File([pastedText], 'pasted-text.txt', {
         type: 'text/plain'
       })
       setSelectedFile(file)
       setInput('')
+      toast.success('Large text converted to file attachment')
     }
-  }
+  }, [selectedFile, setInput])
🤖 Prompt for AI Agents
In `@components/chat-panel.tsx` around lines 80 - 96, Add a success toast when
large pasted text is converted to a File: inside handlePaste, after creating the
File and calling setSelectedFile(file) and setInput(''), call toast.success with
a brief message like "Pasted text attached as file" so the user sees the
conversion. Also wrap handlePaste in React.useCallback (same pattern as
debouncedGetSuggestions) and include its dependencies (selectedFile,
setSelectedFile, setInput, toast) to avoid unnecessary re-renders and keep prop
stability for any memoized Textarea component.

@ngoiyaeric
Copy link
Collaborator Author

@coderabbitai the pasted texts supposed to turn into an attachment as soon as pasted leaving the chat input empty for follow up text

@google-labs-jules
Copy link
Contributor

@coderabbitai the pasted texts supposed to turn into an attachment as soon as pasted leaving the chat input empty for follow up text

I have implemented this behavior in my latest commit. When a text block exceeding 5,000 characters is pasted, it is now automatically converted into a pasted-text.txt attachment and the chat input is cleared immediately. I've also ensured that if an attachment already exists, the paste is prevented and a notification is shown to keep the input clean.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the chat-panel.tsx file to examine the handlePaste implementation
cat components/chat-panel.tsx

Length of output: 10540


@ngoiyaeric I see the issue! The current implementation calls setInput('') which clears the entire input, including any text you may have typed before pasting. However, since you're already calling e.preventDefault() to prevent the default paste behavior, you don't need to call setInput('') at all.

Here's the corrected handlePaste function:

const handlePaste = (e: React.ClipboardEvent) => {
  const pastedText = e.clipboardData.getData('text')
  if (pastedText.length > 5000) {
    e.preventDefault() // This prevents the paste from happening
    if (selectedFile) {
      toast.error(
        'Please remove the current attachment to convert large paste to file'
      )
      return
    }
    const file = new File([pastedText], 'pasted-text.txt', {
      type: 'text/plain'
    })
    setSelectedFile(file)
    // Removed setInput('') - e.preventDefault() already stops the paste
  }
}

What this fixes:

  • e.preventDefault() already prevents the large pasted text from appearing in the input
  • Removing setInput('') preserves any text you typed before pasting (e.g., "Here's my large dataset: " + paste → the input keeps "Here's my large dataset: " and you can continue typing)
  • If the input was empty before pasting, it remains empty
  • The pasted content becomes a file attachment in both cases

This way, you can add context before or after attaching the large pasted text file! 🐰

Task: Fix handlePaste to preserve existing input text

The fix removes the unnecessary setInput('') call since e.preventDefault() already prevents the pasted text from being inserted.


💡 Use @coderabbitai plan to create a plan for code changes

…andling and input clearing

This commit ensures:
- Large pastes (>5000 chars) are converted to a file attachment.
- Chat input is cleared upon conversion to allow for follow-up text.
- Paste is prevented (preventDefault) even on collision to keep UI clean.
- All validation uses sonner toasts for consistent UI.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
@ngoiyaeric
Copy link
Collaborator Author

@coderabbitai So this should immediately turn into a file as opposed to a long input where the chat input is empty and ready for an input
Screenshot 2026-02-01 224039

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the current implementation to see what's actually in the file
cat components/chat-panel.tsx

Length of output: 10540


@ngoiyaeric I see the issue in the screenshot - the pasted JSON text is appearing in the chat input instead of immediately becoming a file attachment.

The current code should work, but there's likely a race condition between React's paste and change events. Even with e.preventDefault() and setInput(''), the controlled input's onChange handler might fire and insert the pasted text before the prevention takes effect.

Here's a more robust solution using a ref to track when we're converting a large paste:

const [selectedFile, setSelectedFile] = useState<File | null>(null)
const isConvertingPaste = useRef(false)

const handlePaste = (e: React.ClipboardEvent) => {
  const pastedText = e.clipboardData.getData('text')
  if (pastedText.length > 5000) {
    e.preventDefault()
    if (selectedFile) {
      toast.error(
        'Please remove the current attachment to convert large paste to file'
      )
      return
    }
    isConvertingPaste.current = true
    const file = new File([pastedText], 'pasted-text.txt', {
      type: 'text/plain'
    })
    setSelectedFile(file)
    setInput('')
    // Reset the flag after React processes the state update
    setTimeout(() => {
      isConvertingPaste.current = false
    }, 0)
  }
}

// In the onChange handler for the Textarea:
onChange={e => {
  // Ignore input changes while converting a large paste
  if (isConvertingPaste.current) {
    return
  }
  setInput(e.target.value)
  debouncedGetSuggestions(e.target.value)
}}

What this fixes:

  • The ref prevents the onChange handler from updating the input while we're converting the paste
  • This ensures the input stays empty after the file is created
  • The pasted content immediately becomes a file attachment (pasted-text.txt) visible below the input
  • The input remains empty and ready for you to type additional context

This should give you the exact behavior shown in your requirement: paste large text → instant file attachment → empty input ready for typing! 🐰

@google-labs-jules
Copy link
Contributor

@coderabbitai So this should immediately turn into a file as opposed to a long input where the chat input is empty and ready for an input
Screenshot 2026-02-01 224039

Understood! In the current implementation, the threshold is set to 5,000 characters. If the text in your screenshot is shorter than that, it will still appear in the input as shown.

Would you like me to lower the threshold? For example, to 1,000 or 2,000 characters? Or should it trigger if the text is simply multi-line and exceeds the visible area of the input (e.g., more than 5 lines)?

- Lowered the threshold for automatic file conversion from 5,000 to 1,000 characters to better catch long inputs.
- Added a 10MB limit check for pasted content to ensure consistency with file upload limits.
- Ensured input is cleared and default behavior is prevented on all large pastes.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@components/chat-panel.tsx`:
- Around line 84-87: The pasted-text size check uses pastedText.length (UTF-16
code units) instead of byte length; update the validation in the paste handling
code to compute the actual byte size (e.g., via
TextEncoder().encode(pastedText).length or new Blob([pastedText]).size) and
compare that byte length to 10 * 1024 * 1024 so it matches the behavior in
handleFileChange; modify the conditional that currently references
pastedText.length to use the computed byteSize and keep the same toast.error and
return flow if it exceeds the limit.
- Line 98: The call to setInput('') in the paste handler is clearing any
pre-typed user text; remove that setInput('') so the existing input is preserved
(locate the onPaste / paste handler in components/chat-panel.tsx and delete the
setInput('') statement), or if you must clear to avoid a race with onChange
implement the ref-based guard discussed in the PR: add a boolean ref (e.g.,
skipOnChangeRef) and set it around the paste flow, then check
skipOnChangeRef.current inside the onChange handler to ignore the spurious
update instead of wiping the input.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a54a27 and 5b0855e.

📒 Files selected for processing (1)
  • components/chat-panel.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/chat-panel.tsx (1)
tests/images.spec.ts (2)
  • testInfo (124-139)
  • testInfo (55-76)
🔇 Additional comments (3)
components/chat-panel.tsx (3)

6-6: LGTM!

The toast import from sonner is correctly added for the new notification functionality.


72-75: LGTM!

Good improvement replacing the browser alert with a toast notification for consistent UX across all validation messages.


275-275: LGTM!

The paste handler is correctly bound to the Textarea.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +84 to +87
if (pastedText.length > 10 * 1024 * 1024) {
toast.error('Pasted text exceeds 10MB limit')
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bug: Size check compares character count against byte threshold.

pastedText.length returns the number of characters (UTF-16 code units), not bytes. For multi-byte Unicode text, the actual byte size can be significantly larger than the character count. This check should validate byte size to be consistent with handleFileChange.

🐛 Proposed fix
   const handlePaste = (e: React.ClipboardEvent) => {
     const pastedText = e.clipboardData.getData('text')
     if (pastedText.length > 1000) {
       e.preventDefault()
-      if (pastedText.length > 10 * 1024 * 1024) {
+      const byteSize = new TextEncoder().encode(pastedText).length
+      if (byteSize > 10 * 1024 * 1024) {
         toast.error('Pasted text exceeds 10MB limit')
         return
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (pastedText.length > 10 * 1024 * 1024) {
toast.error('Pasted text exceeds 10MB limit')
return
}
const byteSize = new TextEncoder().encode(pastedText).length
if (byteSize > 10 * 1024 * 1024) {
toast.error('Pasted text exceeds 10MB limit')
return
}
🤖 Prompt for AI Agents
In `@components/chat-panel.tsx` around lines 84 - 87, The pasted-text size check
uses pastedText.length (UTF-16 code units) instead of byte length; update the
validation in the paste handling code to compute the actual byte size (e.g., via
TextEncoder().encode(pastedText).length or new Blob([pastedText]).size) and
compare that byte length to 10 * 1024 * 1024 so it matches the behavior in
handleFileChange; modify the conditional that currently references
pastedText.length to use the computed byteSize and keep the same toast.error and
return flow if it exceeds the limit.

type: 'text/plain'
})
setSelectedFile(file)
setInput('')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

setInput('') clears any pre-typed text.

If a user types some context before pasting large text, this call erases their typed content. Per PR discussion, e.preventDefault() already stops the pasted text from being inserted, so clearing input may not be necessary.

Consider preserving existing input or showing a success toast so users understand why their input changed.

♻️ Option A: Remove setInput to preserve typed text
       setSelectedFile(file)
-      setInput('')
+      toast.success('Large text converted to file attachment')
♻️ Option B: Add ref guard to prevent race condition (per PR discussion)

If you need to clear input to prevent race conditions with onChange, use a ref to guard against the change event:

+  const isConvertingPaste = useRef(false)
+
   const handlePaste = (e: React.ClipboardEvent) => {
     const pastedText = e.clipboardData.getData('text')
     if (pastedText.length > 1000) {
       e.preventDefault()
       // ... size and attachment checks ...
+      isConvertingPaste.current = true
       const file = new File([pastedText], 'pasted-text.txt', {
         type: 'text/plain'
       })
       setSelectedFile(file)
       setInput('')
+      toast.success('Large text converted to file attachment')
+      // Reset flag after state updates
+      setTimeout(() => { isConvertingPaste.current = false }, 0)
     }
   }

Then guard the onChange:

   onChange={e => {
+    if (isConvertingPaste.current) return
     setInput(e.target.value)
     debouncedGetSuggestions(e.target.value)
   }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setInput('')
setSelectedFile(file)
toast.success('Large text converted to file attachment')
🤖 Prompt for AI Agents
In `@components/chat-panel.tsx` at line 98, The call to setInput('') in the paste
handler is clearing any pre-typed user text; remove that setInput('') so the
existing input is preserved (locate the onPaste / paste handler in
components/chat-panel.tsx and delete the setInput('') statement), or if you must
clear to avoid a race with onChange implement the ref-based guard discussed in
the PR: add a boolean ref (e.g., skipOnChangeRef) and set it around the paste
flow, then check skipOnChangeRef.current inside the onChange handler to ignore
the spurious update instead of wiping the input.

@ngoiyaeric
Copy link
Collaborator Author

@coderabbitai resolution search has stopped working

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Tip

For best results, initiate chat on the files or code changes.

@ngoiyaeric Could you please provide more details about "resolution search"? I'd like to help, but I need to understand:

  1. What is "resolution search"? - Is this a feature in the QCX application, or are you referring to something else?
  2. Where is this feature located? - Which file or component contains this functionality?
  3. Is this related to the paste-to-file changes? - Did it stop working after implementing the large paste conversion feature, or is this a separate issue?
  4. What behavior are you seeing? - What happens when you try to use it, and what did you expect to happen?

Once I understand what you're referring to, I can investigate the issue and help you resolve it! 🐰

@ngoiyaeric ngoiyaeric added help wanted Extra attention is needed Bug fix 1.0.0 Standard release labels Feb 1, 2026
- Intercept paste events in `ChatPanel` and convert text > 500 chars to `pasted-text.txt`.
- Clear the textarea immediately upon conversion.
- Unify toast notifications by switching from `react-toastify` to `sonner` in `ChatPanel` and `HeaderSearchButton`.
- Add E2E tests for paste-to-file conversion logic.
- Fix regression in Resolution Search by ensuring consistent notification library usage.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/paste.spec.ts`:
- Around line 72-78: The second paste dispatch uses document.activeElement which
may not be the chat input after the first paste; before creating/dispatching the
ClipboardEvent for largeText2, explicitly focus the chat input element used
earlier (e.g., call page.focus(selector) or inside page.evaluate call
document.querySelector('<chat-input-selector>').focus()) so the paste event
targets the correct element instead of relying on document.activeElement; update
the block that constructs the DataTransfer/ClipboardEvent (the page.evaluate
invocation that uses largeText2) to focus the intended input element first.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b0855e and 88731f3.

📒 Files selected for processing (3)
  • components/chat-panel.tsx
  • components/header-search-button.tsx
  • tests/paste.spec.ts
🧰 Additional context used
🧬 Code graph analysis (2)
components/chat-panel.tsx (1)
tests/images.spec.ts (1)
  • testInfo (124-139)
components/header-search-button.tsx (4)
components/ui/sonner.tsx (1)
  • props (8-29)
components/ui/toaster.tsx (2)
  • Toaster (6-26)
  • Toast (11-22)
components/ui/hooks/use-toast.tsx (4)
  • useToast (36-89)
  • toasts (60-66)
  • setToasts (40-42)
  • toastId (71-82)
components/ui/toast.tsx (3)
  • props (94-96)
  • props (45-47)
  • props (68-80)
🔇 Additional comments (5)
components/header-search-button.tsx (1)

12-12: LGTM! Toast library standardization.

Switching from react-toastify to sonner aligns with the existing sonner setup in components/ui/sonner.tsx. The toast.error() API is compatible, and no other changes are needed.

tests/paste.spec.ts (1)

1-82: Good test coverage for paste-to-file feature.

The tests cover the three key scenarios: large paste conversion, small paste passthrough, and collision handling. The use of DataTransfer and ClipboardEvent to simulate paste events is appropriate for E2E testing.

components/chat-panel.tsx (3)

6-6: LGTM! Toast library import.

Consistent with the standardization to sonner across the codebase.


69-78: LGTM! File size validation with toast.

The 10MB limit check and toast error notification are correctly implemented for file uploads.


275-275: LGTM! onPaste handler binding.

Correctly binds handlePaste to the Textarea to intercept paste events.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +72 to +78
const largeText2 = 'B'.repeat(501);
await page.evaluate((text) => {
const dt = new DataTransfer();
dt.setData('text/plain', text);
const event = new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true });
document.activeElement?.dispatchEvent(event);
}, largeText2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing focus before second paste may cause test flakiness.

After the first paste creates an attachment, the focus might be lost. The second paste (lines 73-78) dispatches the event to document.activeElement, which may no longer be the chat input.

🛡️ Proposed fix
     await expect(page.getByText('pasted-text.txt')).toBeVisible();

     const largeText2 = 'B'.repeat(501);
+    await chatInput.focus();
     await page.evaluate((text) => {
       const dt = new DataTransfer();
       dt.setData('text/plain', text);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const largeText2 = 'B'.repeat(501);
await page.evaluate((text) => {
const dt = new DataTransfer();
dt.setData('text/plain', text);
const event = new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true });
document.activeElement?.dispatchEvent(event);
}, largeText2);
const largeText2 = 'B'.repeat(501);
await chatInput.focus();
await page.evaluate((text) => {
const dt = new DataTransfer();
dt.setData('text/plain', text);
const event = new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true });
document.activeElement?.dispatchEvent(event);
}, largeText2);
🤖 Prompt for AI Agents
In `@tests/paste.spec.ts` around lines 72 - 78, The second paste dispatch uses
document.activeElement which may not be the chat input after the first paste;
before creating/dispatching the ClipboardEvent for largeText2, explicitly focus
the chat input element used earlier (e.g., call page.focus(selector) or inside
page.evaluate call document.querySelector('<chat-input-selector>').focus()) so
the paste event targets the correct element instead of relying on
document.activeElement; update the block that constructs the
DataTransfer/ClipboardEvent (the page.evaluate invocation that uses largeText2)
to focus the intended input element first.

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

Labels

1.0.0 Standard release Bug fix help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants