Skip to content

[FEATURE]: Paginated message loading for long sessions #6548

Closed as not planned

Description

@CasualDeveloper

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Long sessions can contain thousands of messages, and loading the entire history up front is slow and memory-heavy.

Add paginated message loading so the app and TUI can keep a bounded message window, load older/newer history on demand, and still support jumps to oldest/latest history.

The pagination contract should also be available through the server/API/SDK, and revert/restore flows should remain correct even when the relevant boundary messages are outside the currently loaded window.

Virtualized rendering could still be a separate future optimization, but this issue is focused on paged message loading and bounded client history.

Activity

  1. added
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    perfIndicates a performance issue or need for optimization
    on Dec 31, 2025
  2. github-actions commented on Dec 31, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Feel free to ignore if none of these address your specific case.

  3. CasualDeveloper commented on Jan 1, 2026

    @CasualDeveloper
    ContributorAuthor

    Background

    I originally just wanted to scroll to the beginning of long conversations - that's what led to #6137/#6138 (message_limit config). But as @rekram1-node pointed out, that's a stopgap. The real fix is pagination + virtualization.

    My longest session has 710 messages. With message_limit: 500, I can see more history, but it's slower to load and uses more memory. Related: #6172 (CPU issues), #4918 (original pagination request).

    Proposed Approach

    After digging into the codebase with Claude (my experience is mostly in Java/backend stuff, so I needed some assistance):

    Phase 1: Paginated Loading (MVP)

    Task File Change
    1.1 session/index.ts:262 Add before cursor param to Session.messages()
    1.2 server/server.ts:1161 Add before query param to endpoint
    1.3 sdk/* Regenerate types
    1.4 tui/context/sync.tsx Add pagination state tracking (hasMore, oldestId, loading)
    1.5 tui/context/sync.tsx Add loadMore(sessionID) method
    1.6 tui/routes/session/index.tsx Trigger loadMore when scrolling near top

    Phase 2: Virtualization (later)

    • Only render visible messages + buffer, spacers for the rest
    • Would need custom implementation - Claude says opentui's viewportCulling skips painting but still mounts all Solid components + Yoga layout nodes
    • Anchor-based scroll preservation on prepend
    • Update findNextVisibleMessage() to work with windowed children

    Recommend Phase 1 first - simpler, solves 80% of the problem.

    The Tricky Part

    The API currently returns an array. Changing to { messages, hasMore } would break SDK users (@opencode-ai/sdk is published on npm).

    Options:

    Option Approach Tradeoff
    1 Add optional paginated param that opts into new response shape Cleanest typing, but two response shapes to maintain
    2 Return pagination info via Link header (body stays array) Industry standard (GitHub uses this, RFC 8288) - fully backwards compatible
    3 Breaking change (bump SDK major) Simplest code, but affects external SDK users

    Claude found that GitHub uses Link headers for pagination (RFC 8288). Option 2 seems like the safest path?

    Questions

    1. Does this approach make sense?
    2. Which option do you prefer for the API?
    3. Any concerns before I start on a PR?
  4. ry2009 commented on Jan 2, 2026

    @ry2009
    Contributor

    did something along th esimilar lines: #6656

  5. CasualDeveloper commented on Jan 4, 2026

    @CasualDeveloper
    ContributorAuthor

    did something along similar lines: #6656

    Nice work! Your implementation seems to align closely with what was proposed above - my Claude says you followed the right approach.

    An observation I noticed after reviewing the diff:

    Claude also offered some minor suggestions (non-blocking, could be follow-ups):

    1. Silent loadMore failures (sync.tsx:377) - The catch block just flips loading to false. Users get no feedback if pagination fails, and can keep triggering broken requests. A toast or short backoff would help.
    2. Brittle Link header parsing (sync.tsx:361) - includes('rel="next"') works with current server output, but won't handle rel=next (no quotes) or multiple Link values. Could parse more defensively or use a small helper.
    3. before cursor validation (server.ts:1162) - Invalid cursors currently fail inside Session.messages. Validating with Identifier.schema("message") in the query schema would return consistent 400s at the boundary.
    4. N+1 queries on deep pagination (session/index.ts:274) - Each page does Storage.list + per-message MessageV2.get, so page 10 of a 1000-message session still scans all IDs. Probably fine for MVP, but could cache IDs or extend Storage for cursor-based listing later.
    5. What this doesn't solve yet (as you noted in your PR's comments):
      CPU/memory will still grow as users paginate since all loaded messages render. Virtualization (Phase 2) would address High CPU usage (100%+) during LLM streaming in long sessions due to O(n) text buffer rendering #6172. But that's a reasonable follow-up - this PR solves the "can't scroll to beginning" problem which is the more urgent issue.

    @ry2009 Happy to help test or iterate on this. If it's easier, I can submit these fixes as commits to your branch - just let me know.

  6. CasualDeveloper commented on Jan 14, 2026

    @CasualDeveloper
    ContributorAuthor

    Update: PR #8535 is now rebased/cleaned and force-pushed at head 49838f685.

    Latest state includes:

    • bi-directional cursor pagination (before / after / oldest) in session + API layers with RFC 8288 Link headers
    • boundary-aware TUI loading for mouse and command-path scrolling (PgUp/PgDn/line/half-page/Home/End)
    • normalized pagination error rendering (non-Error throws no longer show [object Object])
    • edge-aware boundary hints with refreshed edge state after async/programmatic scroll updates
    • revert-marker preservation on oldest/latest jumps
    • regression coverage for session/API/link-header and TUI pagination helper flows

    Validation on this branch:

    • bun --cwd packages/opencode test test/session/messages-pagination.test.ts test/server/session-messages.test.ts test/cli/tui/pagination-helpers.test.ts test/util/parse-link-header.test.ts (32 pass)
    • bun turbo typecheck (pass)

    Related:

  7. changed the title [-]feat: virtualized scrolling + paginated message loading for long sessions[/-] [+]feat: virtualized scrolling + paginated message loading for long sessions (#8535)[/+] on Jan 14, 2026
  8. 12 remaining items

  9. changed the title [-]feat: virtualized scrolling + paginated message loading for long sessions (#8535)[/-] [+][FEATURE] Virtualized scrolling + paginated message loading for long sessions (#8535)[/+] on Jan 25, 2026
  10. CasualDeveloper commented on Feb 18, 2026

    @CasualDeveloper
    ContributorAuthor

    Linked PR #8535 updated to work with new sqlite-backed message storage in latest OpenCode dev commit (currently 6cd3a59, several commits newer then v1.2.6)

  11. CasualDeveloper commented on Apr 18, 2026

    @CasualDeveloper
    ContributorAuthor

    Rebased PR #8535 onto the latest dev again.

    Still applies on current message pagination semantics, and I also fixed the reverted-session boundary hydration edge case on the updated branch.

    Verified locally with bun turbo typecheck, bun test --timeout 60000 test/session/messages-pagination.test.ts, bun test --timeout 60000 test/server/session-messages.test.ts, bun test --timeout 60000 test/server/session-select.test.ts, bun test --timeout 60000 test/cli/tui/pagination-helpers.test.ts, and bun test --timeout 60000 test/util/parse-link-header.test.ts.

  12. changed the title [-][FEATURE] Virtualized scrolling + paginated message loading for long sessions (#8535)[/-] [+][FEATURE]: Paginated message loading for long sessions[/+] on May 2, 2026
  13. removed
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    perfIndicates a performance issue or need for optimization
    on May 3, 2026
  14. CasualDeveloper commented on Jun 6, 2026

    @CasualDeveloper
    ContributorAuthor

    Quick update: the fork branch has been refreshed and synced to origin/dev at 4519a1d.

  15. D1ChangGeng commented on Jun 24, 2026

    @D1ChangGeng

    +context on v1.17.9: the web/desktop packages/app has cursor-based loadMore (directory-sync.ts), but the TUI (packages/tui) has no equivalent — it loads a single limit: 100 page and stops. This is the upstream cause of #28257 (undo) and #30587 (fork-from-history). PR #8535 would close the gap; thanks for keeping it rebased @CasualDeveloper.

  16. CasualDeveloper commented on Jul 9, 2026

    @CasualDeveloper
    ContributorAuthor

    Quick update: the fork branch has been refreshed and synced to origin/dev at 77429f5.

  17. github-actions commented on Sep 8, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions