From 40f42403890f3b89429e00a71e32703edcee23aa Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 14 Aug 2026 19:12:17 -0700 Subject: [PATCH] fix(mobile): isolate image echo occurrences by count (cherry picked from commit c51e8e6bbbb9099a3be82c422f52b3037b36a194) --- ...le-native-chat-literal-marker-echo.test.ts | 42 +++++++++++++++++++ .../mobile-native-chat-pending-echo.ts | 5 ++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/mobile/src/session/mobile-native-chat-literal-marker-echo.test.ts b/mobile/src/session/mobile-native-chat-literal-marker-echo.test.ts index 77ad99005ba..660a6c20282 100644 --- a/mobile/src/session/mobile-native-chat-literal-marker-echo.test.ts +++ b/mobile/src/session/mobile-native-chat-literal-marker-echo.test.ts @@ -198,4 +198,46 @@ describe('mobile literal image-marker turns', () => { ['file:///a.jpg', 'file:///b.jpg', 'file:///c.jpg'] ]) }) + + it('reconciles rapid equal captions in separate image-count namespaces', async () => { + await mount() + send('compare', ['file:///a.jpg']) + send('compare', ['file:///b.jpg', 'file:///c.jpg', 'file:///d.jpg']) + + const messages = [ + userTextMessage('source-a', '[Image: source: /tmp/a.png]'), + userTextMessage('prompt-a', 'compare [Image #1]'), + userTextMessage('source-b', '[Image: source: /tmp/b.png]'), + userTextMessage('source-c', '[Image: source: /tmp/c.png]'), + userTextMessage('source-d', '[Image: source: /tmp/d.png]'), + userTextMessage('prompt-b', 'compare [Image #1] [Image #2] [Image #3]') + ] + await transcript(messages) + + const rendered = rows(messages) + expect(rendered).toHaveLength(2) + expect(rendered.map(rowImages)).toEqual([ + ['file:///a.jpg'], + ['file:///b.jpg', 'file:///c.jpg', 'file:///d.jpg'] + ]) + }) + + it('reconciles rapid captionless sends in separate image-count namespaces', async () => { + await mount() + send('', ['file:///a.jpg']) + send('', ['file:///b.jpg', 'file:///c.jpg', 'file:///d.jpg']) + + const messages = [ + userTextMessage('prompt-a', '[Image #1]'), + userTextMessage('prompt-b', '[Image #1] [Image #2] [Image #3]') + ] + await transcript(messages) + + const rendered = rows(messages) + expect(rendered).toHaveLength(2) + expect(rendered.map(rowImages)).toEqual([ + ['file:///a.jpg'], + ['file:///b.jpg', 'file:///c.jpg', 'file:///d.jpg'] + ]) + }) }) diff --git a/mobile/src/session/mobile-native-chat-pending-echo.ts b/mobile/src/session/mobile-native-chat-pending-echo.ts index b5fb6ab4f90..a40c5c4bc75 100644 --- a/mobile/src/session/mobile-native-chat-pending-echo.ts +++ b/mobile/src/session/mobile-native-chat-pending-echo.ts @@ -52,13 +52,16 @@ export function appendMobileNativeChatPending( const earlierOutstanding = current.filter( (pending) => pendingMatchText(pending) === origin.normalizedText && + (pending.images?.length ?? 0) >= origin.imageCount && pending.expectedOccurrence > origin.baselineOccurrences ).length // An image send whose caption is only markers still reconciles by image // ordinal, matching how `captureSendOrigin` normalized it. const expectedImageEchoOrdinal = current.filter( - (pending) => pending.images?.length && normalizeNativeChatUserText(pending.text) === '' + (pending) => + (pending.images?.length ?? 0) >= origin.imageCount && + normalizeNativeChatUserText(pending.text) === '' ).length + 1 return { ...previous,