From 30f45d6ec794ff2666cea9540d264a5c0a55da01 Mon Sep 17 00:00:00 2001 From: Shrinivas Biradar Date: Mon, 25 May 2026 15:29:33 +0530 Subject: [PATCH 1/2] mergeTooShort() never merges a short first segment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function merges short segments into the preceding one using this guard: if (result.length > 0 && part.length < MIN_SEGMENT_CHARS) When the very first segment is short (e.g. "Ok."), result.length === 0, so it's pushed as-is. The second segment then passes the length check and gets pushed separately — leaving a tiny stub bubble as the first chat message. Fix: After the main loop, if result[0] is still below the minimum, forward-merge it into result[1]. --- app/src/utils/messageSegmentation.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/utils/messageSegmentation.ts b/app/src/utils/messageSegmentation.ts index baf33c450e..4a91af14ff 100644 --- a/app/src/utils/messageSegmentation.ts +++ b/app/src/utils/messageSegmentation.ts @@ -59,7 +59,18 @@ export function getSegmentDelay(segment: string): number { // ─── helpers ───────────────────────────────────────────────────────────────── -/** Merge adjacent items that are shorter than MIN_SEGMENT_CHARS. */ +/** + * Merge adjacent items that are shorter than MIN_SEGMENT_CHARS. + * + * Previously only short segments following a prior segment were merged (into + * the preceding one). A short *first* segment was left as-is because the + * `result.length > 0` guard prevented it from being absorbed. This caused the + * first chat bubble to show fewer than MIN_SEGMENT_CHARS characters while all + * subsequent short segments were correctly consolidated. + * + * Fix: after the normal backward-merge pass, check whether the first segment + * is still too short and, if so, forward-merge it into the second segment. + */ function mergeTooShort(parts: string[], joiner: string): string[] { const result: string[] = []; for (const part of parts) { @@ -69,6 +80,14 @@ function mergeTooShort(parts: string[], joiner: string): string[] { result.push(part); } } + + // If the first segment is still shorter than the minimum, forward-merge it + // into the second segment (if one exists) so no leading stub bubble is shown. + if (result.length >= 2 && result[0].length < MIN_SEGMENT_CHARS) { + result[1] = result[0] + joiner + result[1]; + result.shift(); + } + return result; } From 781bf66f4f3a17baec825eb3b91b437f69923a76 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Thu, 28 May 2026 14:46:18 -0700 Subject: [PATCH 2/2] test(messageSegmentation): cover short first-segment forward-merge (addresses @coderabbitai) --- .../__tests__/messageSegmentation.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/src/utils/__tests__/messageSegmentation.test.ts b/app/src/utils/__tests__/messageSegmentation.test.ts index d49236c23c..5f9ae92d75 100644 --- a/app/src/utils/__tests__/messageSegmentation.test.ts +++ b/app/src/utils/__tests__/messageSegmentation.test.ts @@ -33,6 +33,32 @@ describe('segmentMessage', () => { expect(segments.every(s => s.length >= 40)).toBe(true); }); + it('merges a short first paragraph into the second paragraph', () => { + const text = + 'Ok.\n\n' + + 'This second paragraph is intentionally long enough to stand alone after merging.'; + const segments = segmentMessage(text); + + expect(segments.length).toBe(1); + expect(segments[0].startsWith('Ok.\n\n')).toBe(true); + expect(segments[0].length).toBeGreaterThanOrEqual(40); + }); + + it('forward-merges a short first paragraph yet keeps the remaining bubbles', () => { + // Three paragraphs where only the first is below MIN_SEGMENT_CHARS. This + // exercises the forward-merge branch in mergeTooShort while still returning + // multiple segments from the paragraph-split strategy (no leading stub). + const text = + 'Ok.\n\n' + + 'This second paragraph is intentionally long enough to stand alone as a bubble.\n\n' + + 'And a third paragraph that is also long enough to remain its own standalone bubble.'; + const segments = segmentMessage(text); + + expect(segments).toHaveLength(2); + expect(segments[0].startsWith('Ok.\n\n')).toBe(true); + expect(segments.every(s => s.length >= 40)).toBe(true); + }); + it('splits on sentence boundaries when no paragraph breaks exist', () => { const text = 'This is the first sentence with some content. This is the second sentence with more content. ' +