Skip to content

fix(chat): page older history in on open and across pages without a turn opener - #1814

Open
f-liva wants to merge 1 commit into
slopus:mainfrom
f-liva:fix/chat-history-paging
Open

f-liva wants to merge 1 commit into
slopus:mainfrom
f-liva:fix/chat-history-paging

Conversation

@f-liva

@f-liva f-liva commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Scrolling up in a long chat can stop at the initial 60-message window: nothing older renders and nothing is fetched, even though the server has more history. Two latent bugs in sources/components/ChatList.tsx:

  1. The session-reset effect also runs on mount. It blanks windowRef right after the window memo produced the first window for this render, so requestOlderHistory reads a zero-length window (currentEnd === 0) and bails until some later message change re-runs the memo. A freshly opened chat that receives no new message cannot page its own history in.
  2. A page without a turn opener wedges paging. When a server page lands but windowEndForTurn finds no user prompt to cut at (a long agent turn whose prompt is still on the server), the resume effect keeps awaitingOlderRef raised with nothing in flight, and every later drag returns early on that flag. The chat ends there for good.

Both were masked on main by a side effect: every incoming socket message re-invalidates the session (handleUpdate → onSessionDataUpdated), which re-runs the five-page background prefetch and fills the store behind the list's back. #1801 removes that per-message refresh, which is how these surfaced; the fix stands on its own and is worth having regardless.

Changes

  • Run the session-reset effect only when the session id actually changes, not on mount
  • After a page without a renderable turn boundary, request the next page while pages keep arriving; re-arm the drag when a fetch brought nothing (failed/empty) instead of spinning
  • Add ChatList.paging.test.ts: drives layout/scroll events through a captured FlashList mock and asserts paging on open, across a no-opener page, and after an empty fetch

Test plan

  • pnpm typecheck (clean)
  • pnpm exec vitest run sources/components/ChatList.paging.test.ts sources/components/ChatList.test.ts (14 passed; all 3 new tests fail against the current ChatList.tsx)
  • Ran on a self-hosted web deployment: scrolling to the top of a chat with thousands of messages keeps loading older turns, including through a long tool-only agent turn

Related: #1813 (long-lived web tab), #1801.

🤖 Generated with Claude Code

…urn opener

Scrolling up in a long chat stopped at the initial window and never
fetched or rendered more history. Two causes in ChatList:

1. The session-reset effect ran on mount too, blanking windowRef after the
   window memo had already produced the first window. requestOlderHistory
   then read a zero-length window and bailed until some later message
   change re-ran the memo, so a freshly opened chat that received no new
   message could not page at all. The reset now runs only when the session
   id actually changes.

2. When a server page landed without a renderable turn boundary (a long
   agent turn whose user prompt is still on the server), the resume effect
   kept awaitingOlderRef raised with nothing in flight, and every later
   drag returned early on that flag. It now requests the next page while
   pages keep arriving, and re-arms the drag when a fetch brought nothing.

Both were masked before: every incoming socket message re-invalidated the
session and re-ran the five-page background prefetch, which filled the
store behind the list's back.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant