You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FEATURE]: Paginated message loading for long sessions #6548
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.
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?
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:
Config interaction with tui.message_limit
My PR feat(tui): add session_list_limit for session picker #6138 adds a message_limit config option. This PR hard-codes limit: 100 in both the initial load (sync.tsx:356) and loadMore (sync.tsx:387), and removes the 100-message trim. Should the page size use the config value instead? Or if pagination makes that config obsolete, we should probably close feat(tui): add session_list_limit for session picker #6138 and remove/deprecate it.
Claude also offered some minor suggestions (non-blocking, could be follow-ups):
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.
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.
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.
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.
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.
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)
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
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
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)
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.
changed the title [-][FEATURE] Virtualized scrolling + paginated message loading for long sessions (#8535)[/-][+][FEATURE]: Paginated message loading for long sessions[/+]on May 2, 2026
+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.
Activity
github-actions commentedon Dec 31, 2025
This issue might be a duplicate of existing issues. Please check:
Feel free to ignore if none of these address your specific case.
CasualDeveloper commentedon Jan 1, 2026
Background
I originally just wanted to scroll to the beginning of long conversations - that's what led to #6137/#6138 (
message_limitconfig). 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)
session/index.ts:262beforecursor param toSession.messages()server/server.ts:1161beforequery param to endpointsdk/*tui/context/sync.tsxhasMore,oldestId,loading)tui/context/sync.tsxloadMore(sessionID)methodtui/routes/session/index.tsxloadMorewhen scrolling near topPhase 2: Virtualization (later)
viewportCullingskips painting but still mounts all Solid components + Yoga layout nodesfindNextVisibleMessage()to work with windowed childrenRecommend 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/sdkis published on npm).Options:
paginatedparam that opts into new response shapeLinkheader (body stays array)Claude found that GitHub uses
Linkheaders for pagination (RFC 8288). Option 2 seems like the safest path?Questions
ry2009 commentedon Jan 2, 2026
did something along th esimilar lines: #6656
CasualDeveloper commentedon Jan 4, 2026
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:
tui.message_limitMy PR feat(tui): add session_list_limit for session picker #6138 adds a
message_limitconfig option. This PR hard-codeslimit: 100in both the initial load (sync.tsx:356) andloadMore(sync.tsx:387), and removes the 100-message trim. Should the page size use the config value instead? Or if pagination makes that config obsolete, we should probably close feat(tui): add session_list_limit for session picker #6138 and remove/deprecate it.Claude also offered some minor suggestions (non-blocking, could be follow-ups):
loadMorefailures (sync.tsx:377) - The catch block just flipsloadingto false. Users get no feedback if pagination fails, and can keep triggering broken requests. A toast or short backoff would help.sync.tsx:361) -includes('rel="next"')works with current server output, but won't handlerel=next(no quotes) or multiple Link values. Could parse more defensively or use a small helper.beforecursor validation (server.ts:1162) - Invalid cursors currently fail insideSession.messages. Validating withIdentifier.schema("message")in the query schema would return consistent 400s at the boundary.session/index.ts:274) - Each page doesStorage.list+ per-messageMessageV2.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.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.
CasualDeveloper commentedon Jan 14, 2026
Update: PR #8535 is now rebased/cleaned and force-pushed at head
49838f685.Latest state includes:
before/after/oldest) in session + API layers with RFC 8288 Link headers[object Object])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:
[-]feat: virtualized scrolling + paginated message loading for long sessions[/-][+]feat: virtualized scrolling + paginated message loading for long sessions (#8535)[/+]12 remaining items
[-]feat: virtualized scrolling + paginated message loading for long sessions (#8535)[/-][+][FEATURE] Virtualized scrolling + paginated message loading for long sessions (#8535)[/+]CasualDeveloper commentedon Feb 18, 2026
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)
CasualDeveloper commentedon Apr 18, 2026
Rebased PR
#8535onto the latestdevagain.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, andbun test --timeout 60000 test/util/parse-link-header.test.ts.[-][FEATURE] Virtualized scrolling + paginated message loading for long sessions (#8535)[/-][+][FEATURE]: Paginated message loading for long sessions[/+]CasualDeveloper commentedon Jun 6, 2026
Quick update: the fork branch has been refreshed and synced to origin/dev at 4519a1d.
D1ChangGeng commentedon Jun 24, 2026
+context on v1.17.9: the web/desktop
packages/apphas cursor-basedloadMore(directory-sync.ts), but the TUI (packages/tui) has no equivalent — it loads a singlelimit: 100page 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.CasualDeveloper commentedon Jul 9, 2026
Quick update: the fork branch has been refreshed and synced to origin/dev at 77429f5.
github-actions commentedon Sep 8, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.