diff --git a/ui-tui/src/app.tsx b/ui-tui/src/app.tsx index 76dd78b543..e2df73a2e8 100644 --- a/ui-tui/src/app.tsx +++ b/ui-tui/src/app.tsx @@ -29,6 +29,7 @@ import { isTransientTrailLine, pick, sameToolTrailGroup, + stripTrailingPasteNewlines, toolTrailLabel } from './lib/text.js' import { DEFAULT_THEME, fromSkin, type Theme } from './theme.js' @@ -784,14 +785,19 @@ export function App({ gw }: { gw: GatewayClient }) { void paste(true) } - if (!text) { + const cleanedText = stripTrailingPasteNewlines(text) + + if (!cleanedText) { return null } - const lineCount = text.split('\n').length + const lineCount = cleanedText.split('\n').length - if (text.length < LARGE_PASTE.chars && lineCount < LARGE_PASTE.lines) { - return { cursor: cursor + text.length, value: value.slice(0, cursor) + text + value.slice(cursor) } + if (cleanedText.length < LARGE_PASTE.chars && lineCount < LARGE_PASTE.lines) { + return { + cursor: cursor + cleanedText.length, + value: value.slice(0, cursor) + cleanedText + value.slice(cursor) + } } pasteCounterRef.current++ @@ -806,13 +812,13 @@ export function App({ gw }: { gw: GatewayClient }) { [ ...prev, { - charCount: text.length, + charCount: cleanedText.length, createdAt: Date.now(), id, - kind: classifyPaste(text), + kind: classifyPaste(cleanedText), lineCount, mode, - text + text: cleanedText } ].slice(-24) ) diff --git a/ui-tui/src/lib/text.test.ts b/ui-tui/src/lib/text.test.ts new file mode 100644 index 0000000000..1a3800ec76 --- /dev/null +++ b/ui-tui/src/lib/text.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' + +import { stripTrailingPasteNewlines } from './text.js' + +describe('stripTrailingPasteNewlines', () => { + it('removes trailing newline runs from pasted text', () => { + expect(stripTrailingPasteNewlines('alpha\n')).toBe('alpha') + expect(stripTrailingPasteNewlines('alpha\nbeta\n\n')).toBe('alpha\nbeta') + }) + + it('preserves interior newlines', () => { + expect(stripTrailingPasteNewlines('alpha\nbeta\ngamma')).toBe('alpha\nbeta\ngamma') + }) + + it('preserves newline-only pastes', () => { + expect(stripTrailingPasteNewlines('\n\n')).toBe('\n\n') + }) +}) diff --git a/ui-tui/src/lib/text.ts b/ui-tui/src/lib/text.ts index 9bed6c3c15..b38b8fbd27 100644 --- a/ui-tui/src/lib/text.ts +++ b/ui-tui/src/lib/text.ts @@ -49,6 +49,8 @@ export const thinkingPreview = (reasoning: string, mode: ThinkingMode, max: numb return !text || mode === 'collapsed' ? '' : mode === 'full' ? text : compactPreview(text, max) } +export const stripTrailingPasteNewlines = (text: string) => (/[^\n]/.test(text) ? text.replace(/\n+$/, '') : text) + export const toolTrailLabel = (name: string) => name .split('_')