Skip to content

Conversation

@CasualDeveloper
Copy link
Contributor

@CasualDeveloper CasualDeveloper commented Jan 14, 2026

Summary

This PR implements bi-directional cursor-based pagination for message loading to support long session histories without performance degradation. Closes #6548.

  • Backend: before/after/oldest cursor support with RFC 5005 Link headers
  • TUI: Infinite scroll with memory bounding (500 messages), Home/End jump navigation
  • Tests: 18 tests across 3 test files

Context

Supersedes #6656 by @ry2009 (earlier partial implementation). This PR is a ground-up rewrite that adds:

  • Bi-directional pagination (both before and after cursors)
  • oldest param for jumping to beginning of history
  • Proper parseLinkHeader utility instead of brittle string matching
  • Memory bounding to prevent unbounded growth
  • Error state feedback instead of silent failures
  • RFC 5005 compliant Link header semantics

Changes

Backend (session/index.ts, server/server.ts, message-v2.ts)

  • Add before, after, and oldest cursor params to Session.messages()
  • Add ascending option to MessageV2.stream() for oldest-first iteration
  • Add Binary.lowerBound to util for efficient cursor lookup
  • Return 400 when mutually exclusive cursors specified
  • RFC 5005 Link headers: rel="prev" → older, rel="next" → newer

TUI (sync.tsx, routes/session/index.tsx)

  • message_page state tracking (hasOlder, hasNewer, loading, error)
  • loadOlder/loadNewer with scroll position restoration
  • jumpToOldest (Home key) / jumpToLatest (End key) for absolute navigation
  • Revert-aware: jumpToLatest preserves revert marker when jumping to newest
  • 500-message sliding window with 50-message eviction chunks
  • Skip SSE inserts when detached from head (hasNewer=true)
  • Race condition prevention: check page state before acquiring loading guard

Tests

File Tests Coverage
messages-pagination.test.ts 5 Cursor, boundary, deletion
session-messages.test.ts 6 API validation, Link headers, oldest param
parse-link-header.test.ts 9 RFC 8288 parsing edge cases

Testing

bun test test/session/messages-pagination.test.ts test/server/session-messages.test.ts test/util/parse-link-header.test.ts
# 18 pass, 0 fail

Related

@github-actions
Copy link
Contributor

The following comment was made by an LLM, it may be inaccurate:

Potential Duplicate Found

PR #6656: "session: paginate message loading"

This is not a duplicate in the traditional sense—it's a more complete implementation that builds upon and replaces the earlier approach.

@CasualDeveloper CasualDeveloper changed the title feat(session): add bi-directional cursor-based pagination for messages feat(session): bi-directional cursor-based pagination (#6548) Jan 14, 2026
@CasualDeveloper
Copy link
Contributor Author

@rekram1-node hi Aiden, would appreciate your feedback on how I implemented pagination, per your suggestion! :)

@ry2009
Copy link
Contributor

ry2009 commented Jan 14, 2026

Glad I could help kickstart this, I have been a little busy to continue my OSS journey right now. I haven't personally looked through the code in depth (just did a skim) but 1k line changes seems like a lot for one PR (might be better to split it if you can) but once again maybe the code is super clean and simple -- I just haven't sifted through deeply lol. Great work & good luck closing this!

@CasualDeveloper CasualDeveloper force-pushed the feat/6548-message-pagination branch 6 times, most recently from 4f57718 to afe9c53 Compare January 15, 2026 06:35
@CasualDeveloper CasualDeveloper force-pushed the feat/6548-message-pagination branch 3 times, most recently from 4bd8bf6 to 34eea60 Compare January 15, 2026 17:21
CasualDeveloper added a commit to CasualDeveloper/opencode that referenced this pull request Jan 15, 2026
Adds session_list_limit config option to limit sessions displayed in the session list dialog (default: 150).

Limit only applies when not searching; search uses server-side limit of 30.

Remove message_limit as it conflicts with cursor-based pagination (anomalyco#8535) and is now redundant.
ryanwyler added a commit to gignit/opencode that referenced this pull request Jan 15, 2026
Phase 4-6: Complete auto-scroll implementation
- Add KV signal for runtime toggle (persists across sessions)
- Add historyConfig memo with default values
- Implement loadOlder() function:
  * Checks both KV toggle and config.enabled
  * Triggers when scroll position <= load_threshold pixels from top
  * Uses existing loadConversationHistory() with ts_before API
- Implement updateVisibleMessageViews() for batch tracking:
  * Updates lastViewed timestamp for all visible messages
  * Called on scroll events for efficient viewport detection
- Add scroll event handlers:
  * onMouseScroll: triggers auto-load and view tracking
  * onKeyDown: triggers auto-load on up/pageup/home keys
- Add command menu toggle option:
  * Dynamic title based on current state
  * Starts/stops cleanup worker on toggle
  * Uses KV system as designed
- Add lifecycle management:
  * createEffect monitors config and KV toggle
  * Starts worker when both enabled and session active
  * onCleanup ensures worker stops on unmount
- Import onCleanup from solid-js

Complete feature implementation: ~190 lines vs PR anomalyco#8535's ~400 lines
@CasualDeveloper CasualDeveloper force-pushed the feat/6548-message-pagination branch from 34eea60 to 2c70c15 Compare January 17, 2026 05:23
CasualDeveloper added a commit to CasualDeveloper/opencode that referenced this pull request Jan 17, 2026
Adds session_list_limit config option to limit sessions displayed in the session list dialog (default: 150).

Limit only applies when not searching; search uses server-side limit of 30.

Remove message_limit as it conflicts with cursor-based pagination (anomalyco#8535) and is now redundant.
@CasualDeveloper CasualDeveloper force-pushed the feat/6548-message-pagination branch from 2c70c15 to adec748 Compare January 17, 2026 10:15
…nd navigation

Implements cursor-based pagination for message loading to handle long sessions without memory explosion with absolute navigation via Home/End keys.

API changes:
- Add 'before' cursor param: fetch messages older than cursor (newest first)
- Add 'after' cursor param: fetch messages newer than cursor (oldest first)
- Add 'oldest' param: start from oldest messages (for jumpToOldest)
- Link headers with rel="prev"/"next" for cursor discovery (RFC 5005)

TUI changes:
- loadOlder/loadNewer actions with sliding window eviction (500 msg limit)
- jumpToOldest (Home): fetches oldest page via ?oldest=true
- jumpToLatest (End): fetches newest page, preserves revert marker
- Detached mode: ignores SSE when viewing history to prevent gaps

Implementation:
- Binary.lowerBound for efficient cursor lookup
- parseLinkHeader utility for RFC 5988 parsing
- Message.stream() reverse option for ascending order
- Smart parts cleanup: only deletes parts for evicted messages

Tests:
- Unit tests for pagination logic and cursor handling
- API tests for before/after/oldest params and Link headers

Resolves: anomalyco#6548
CasualDeveloper added a commit to CasualDeveloper/opencode that referenced this pull request Jan 17, 2026
Adds session_list_limit config option to limit sessions displayed in the session list dialog (default: 150).

Limit only applies when not searching; search uses server-side limit of 30.

Remove message_limit as it conflicts with cursor-based pagination (anomalyco#8535) and is now redundant.
@CasualDeveloper CasualDeveloper force-pushed the feat/6548-message-pagination branch from adec748 to 01e84ef Compare January 17, 2026 10:46
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.

feat: virtualized scrolling + paginated message loading for long sessions (#8535)

2 participants