From 19a28542d858bb5041e951eea24d7d676a4bdd2e Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Tue, 4 Aug 2026 02:23:31 -0700 Subject: [PATCH] test(mobile): lock the history-position hold's release path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tail-follow positive control never drove a paging cycle, so a hold that latched on — chat permanently unable to follow the agent, the worst failure mode of this design — passed the whole suite. Drive the real loadingEarlier -> prepend -> settled sequence before the append. Also record why onContentSizeChange reads the render snapshot instead of the ref: effects flush before native layout, so the ref is already cleared there. --- mobile/src/session/MobileNativeChatView.test.ts | 7 ++++++- mobile/src/session/MobileNativeChatView.tsx | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mobile/src/session/MobileNativeChatView.test.ts b/mobile/src/session/MobileNativeChatView.test.ts index 4214df7f521..5226f483d27 100644 --- a/mobile/src/session/MobileNativeChatView.test.ts +++ b/mobile/src/session/MobileNativeChatView.test.ts @@ -222,7 +222,12 @@ describe('MobileNativeChatView', () => { it('still follows the tail once the history page has settled', async () => { vi.useFakeTimers() try { - await render({ messages: [older, message], hasMore: false }) + await render({ messages: [message], hasMore: true }) + await act(async () => vi.runAllTimersAsync()) + // Drive a real paging cycle first: a hold that never releases would leave + // the chat permanently unable to follow the agent. + await update({ messages: [message], hasMore: true, loadingEarlier: true }) + await update({ messages: [older, message], hasMore: false, loadingEarlier: false }) await act(async () => vi.runAllTimersAsync()) scrollToEnd.mockClear() diff --git a/mobile/src/session/MobileNativeChatView.tsx b/mobile/src/session/MobileNativeChatView.tsx index 857cf3b73b0..21763f0d670 100644 --- a/mobile/src/session/MobileNativeChatView.tsx +++ b/mobile/src/session/MobileNativeChatView.tsx @@ -154,6 +154,8 @@ export function MobileNativeChatView({ // A short transcript leaves the reader both at the top and `atBottom`, so the // tail-follow paths would scroll away the history that just paged in. const keepHistoryPositionRef = useRef(Boolean(loadingEarlier)) + // Snapshot, not a live ref read: effects flush before native layout, so by the + // time `onContentSizeChange` runs the ref is already cleared. const keepHistoryPosition = keepHistoryPositionRef.current const { fontScale, pinchGesture } = useMobileNativeChatPinchGesture() useEffect(