test(mobile): pin the older-page fence against a cursor-re-cutting replay

The snapshot arm of `if (applied.windowReplaced || frame.type === 'snapshot')`
was unpinned: deleting it kept the suite green.

It is load-bearing. A replay that merges cleanly can still carry a new
`beforeOffset`, which the hook adopts via `replayStillStartsAtOldest`. The page
already in flight was addressed with the old offset, so without the fence it
lands and writes its own stale cursor back over the fresh one, leaving the next
`loadEarlier` addressed from a byte offset that no longer describes the file.
Row order alone stays correct, which is why the ordering-only reasoning missed
this.

Sole failure under the mutation. No source change.
This commit is contained in:
Brennan Benson
2026-08-04 03:26:33 -07:00
parent 527432719e
commit 8f7642e7ed
@@ -333,6 +333,38 @@ describe('useMobileNativeChatSession', () => {
expect(state?.hasMore).toBe(false)
})
it('fences an in-flight older page when a merging replay re-cuts the byte cursor', async () => {
let resolveEarlier: (response: unknown) => void = () => {}
const sendRequest = vi.fn(
() => new Promise((resolve) => (resolveEarlier = resolve))
) as unknown as RpcClient['sendRequest']
const retained = Array.from({ length: 40 }, (_unused, index) => message(`win-${index}`))
const subscribe: RpcClient['subscribe'] = vi.fn((_method, _params, onData) => {
emit = onData
onData({ type: 'snapshot', messages: retained, hasMore: true, beforeOffset: 100 })
return () => {}
})
await mount({ sendRequest, subscribe } as unknown as RpcClient)
act(() => state?.loadEarlier())
// The replay merges (same rows, no trim), but the host re-cut the file, so
// it carries a new cursor. The page already in flight was addressed with the
// old offset and would write that stale cursor back over the fresh one.
await act(async () =>
emit({ type: 'snapshot', messages: retained, hasMore: true, beforeOffset: 250 })
)
await act(async () => {
resolveEarlier({
ok: true,
result: { messages: [message('stale-page')], hasMore: true, beforeOffset: 40 }
})
await Promise.resolve()
})
expect(state?.messages.some((entry) => entry.id === 'stale-page')).toBe(false)
expect(state?.loadingEarlier).toBe(false)
})
it('keeps the base snapshot authoritative when a live append arrives first', async () => {
const sendRequest = vi.fn()
const subscribe: RpcClient['subscribe'] = vi.fn((_method, _params, onData) => {