From bf4ac4b1d1a6de00fe9e69370004accbe87d9abe Mon Sep 17 00:00:00 2001 From: ParsaKhaz Date: Sat, 16 May 2026 15:57:55 -0700 Subject: [PATCH 1/2] Fix multiline paste in AI terminal panels --- .../src/components/panels/TerminalPanel.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/panels/TerminalPanel.tsx b/frontend/src/components/panels/TerminalPanel.tsx index 34ea82fc..c51d17a2 100644 --- a/frontend/src/components/panels/TerminalPanel.tsx +++ b/frontend/src/components/panels/TerminalPanel.tsx @@ -822,6 +822,21 @@ export const TerminalPanel: React.FC = React.memo(({ panel, // The "[Image] " prefix we used to add actually broke the parser's // path-detection — on Windows+WSL it caused Claude to cache the file but // never attach it to the API call (see commit 7b76ee5). + const pasteText = (text: string) => { + if (!terminal) return; + const shouldProtectMultilinePaste = isCliPanel && !tuiActiveRef.current && /[\r\n]/.test(text); + if (shouldProtectMultilinePaste) { + window.electronAPI.invoke( + 'terminal:input', + panel.id, + text.replace(/\r\n|\r|\n/g, '\x1b\r'), + ); + return; + } + + terminal.paste(text); + }; + const handlePaste = (e: ClipboardEvent) => { // Step 1: Check for images in browser clipboard (works on native Windows/macOS) const items = e.clipboardData?.items; @@ -895,7 +910,7 @@ export const TerminalPanel: React.FC = React.memo(({ panel, // No image found — forward the text content xterm would have pasted. if (text && !disposed && terminal) { - terminal.paste(text); + pasteText(text); } })(); }; From 22a6ce9245aba82f6be05cdffecd415f1b7bcb91 Mon Sep 17 00:00:00 2001 From: ParsaKhaz Date: Sat, 16 May 2026 16:37:15 -0700 Subject: [PATCH 2/2] fix: use live cli state for multiline paste --- frontend/src/components/panels/TerminalPanel.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/panels/TerminalPanel.tsx b/frontend/src/components/panels/TerminalPanel.tsx index c51d17a2..7f1b96bc 100644 --- a/frontend/src/components/panels/TerminalPanel.tsx +++ b/frontend/src/components/panels/TerminalPanel.tsx @@ -93,6 +93,7 @@ export const TerminalPanel: React.FC = React.memo(({ panel, const terminalState = panel.state?.customState as TerminalPanelState | undefined; const isCliPanel = !!terminalState?.isCliPanel; const [isCliReady, setIsCliReady] = useState(!!terminalState?.isCliReady); + const isCliPanelRef = useRef(isCliPanel); // ptyId for the current PTY behind this panel, delivered via // `terminal:ptyReady` when spawned through the ptyHost UtilityProcess. @@ -119,6 +120,10 @@ export const TerminalPanel: React.FC = React.memo(({ panel, // Sync isCliReady from panel prop when it changes (e.g. backend persisted isCliReady // before this component subscribed to the IPC event, or panel state was updated externally) + useEffect(() => { + isCliPanelRef.current = isCliPanel; + }, [isCliPanel]); + useEffect(() => { if (terminalState?.isCliReady && !isCliReady) { setIsCliReady(true); @@ -824,7 +829,7 @@ export const TerminalPanel: React.FC = React.memo(({ panel, // never attach it to the API call (see commit 7b76ee5). const pasteText = (text: string) => { if (!terminal) return; - const shouldProtectMultilinePaste = isCliPanel && !tuiActiveRef.current && /[\r\n]/.test(text); + const shouldProtectMultilinePaste = isCliPanelRef.current && !tuiActiveRef.current && /[\r\n]/.test(text); if (shouldProtectMultilinePaste) { window.electronAPI.invoke( 'terminal:input',