fix(web): paginate large thread history for remote clients#4018
fix(web): paginate large thread history for remote clients#4018sercada wants to merge 7 commits into
Conversation
A busy SQLite database materialised hundreds of MB of activity payloads into the
Node heap and crashed the server. Bound the reads and add an on-demand
pagination path so deep history is still recoverable.
- /api/orchestration/snapshot and the `t3` CLI offline path called getSnapshot(),
loading every thread's full activity/message/checkpoint history even though
they only read `.projects`. Point both at getCommandReadModel() (same shape,
without the heavy per-thread tables).
- getThreadDetailById windows activities to the most recent 500 (it fetches
WINDOW+1 to detect truncation and sets `hasMoreActivities` so clients can
lazy-load). Live activities still stream in via the event subscription.
- New orchestration.getThreadActivities RPC pages older activities on demand:
cursor is {beforeSequence} for sequenced rows or {beforeCreatedAt,
beforeActivityId} for legacy unsequenced (NULL) rows, output {activities,
hasMore}. The sequenced query orders by `sequence DESC` with
`(sequence < ? OR sequence IS NULL)` so the (thread_id, sequence, created_at,
activity_id) index satisfies the ORDER BY instead of a filesort. Unsequenced
rows page by a (created_at, activity_id) cursor consistent with the window's
ordering.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The server windows thread detail to the most recent 500 activities and reports `hasMoreActivities`. The web timeline now fetches older pages on demand via the getThreadActivities RPC: - client-runtime: loadThreadActivities command on orchestrationEnvironment. - ChatView keeps per-thread older pages in local state, prepends them ahead of the live window, and derives hasMore from the server flag (no client-side window-size constant). A thread-keyed in-flight ref coalesces the duplicate dispatches a fast scroll-to-top would otherwise fire, and a request-key guard discards results after a thread switch. - MessagesTimeline triggers a load on reaching the top (maintainVisibleContentPosition anchors the viewport on prepend) with a "Load older history" header and loading indicator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Detect a live-window reshape from an order-independent oldest boundary instead of activities[0], so the first unsequenced live append does not incorrectly clear paged history. Run the older-pages reset in useLayoutEffect to prevent one stale frame after a thread switch. Keep the shared chronology helper in client-runtime with unit coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6e8cf9b to
2c17c8b
Compare
ApprovabilityVerdict: Needs human review This PR introduces a new pagination feature for thread activities with significant runtime behavior changes (windowing to 500 activities, new RPC endpoint, complex client-side lazy-loading state management). The scope and complexity warrant human review. You can customize Macroscope's approvability policy. Learn more. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c9783d1. Configure here.
|
I'm mostly using this branch which contains all the fixes and a tui https://github.com/olafura/t3code/tree/olafura/simple-tui been pretty busy I have some updates hit another limit 😁 I'm pushing some updates that you can cherry pick if you want |

Summary
Fixes #2761.
This rebases the server and web implementation from #3510 onto current
main, where that PR is now conflicting after the repository history changes. Original implementation commits retain @olafura as their author. It deliberately leaves native mobile out of scope.Why
A live remote-web reproduction showed that the server and Tailscale path returned a 12.6 MB snapshot in about 0.2 seconds, so transport was not the bottleneck. The browser was eagerly hydrating eight visible thread details totaling roughly 38 MB. The largest thread contained 1,758 activities and serialized to 16.4 MB.
With this change, a browser initially hydrates only the selected thread and only its newest 500 activities. In the same dataset that window is about 6.7 MB, while older history remains reachable on demand.
Verification
pnpm exec vp run typecheckpnpm exec vp check— 0 errors; 9 existing nested-component warningspnpm exec vp test apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts— 11 passedpnpm exec vp test packages/client-runtime/src/state/threadReducer.test.ts— 26 passedpnpm exec vp test apps/web/src/components/chat/MessagesTimeline.test.tsx— 15 passedRelated: #3817 addresses heartbeat tolerance, but does not reduce the large history payload or browser processing cost.
Note
Medium Risk
Touches projection read paths, a new RPC surface, and chat timeline state (cursors, dedup, reshape resets); behavior is heavily tested but wrong pagination could hide or duplicate activity rows on long threads.
Overview
Caps thread-detail activity loads to the 500 newest rows and sets
hasMoreActivitieswhen older rows exist, with a neworchestration.getThreadActivitiesRPC (sequencedbeforeSequenceor legacybeforeCreatedAt/beforeActivityIdcursors) to page older history.Web merges lazy-loaded pages into the live window in
ChatView, resets on thread/window reshape, dedupes by id, andMessagesTimelinescroll-up auto-load (edge-triggered to avoid retry loops) plus a manual “Load older history” control. Browser sidebar no longer prewarms thread details; Electron still does.snapshotHTTP and CLI offline resolution switch fromgetSnapshottogetCommandReadModelso project-only consumers avoid loading full per-thread tables.Reviewed by Cursor Bugbot for commit de8fd65. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Paginate large thread history for remote web clients
hasMoreActivitieson threads that have older history beyond this window.orchestration.getThreadActivitiesWebSocket RPC with cursor-based pagination, supporting both sequenced (beforeSequence) and legacy unsequenced (beforeCreatedAt/beforeActivityId) cursors.ChatViewnow lazy-loads and prepends older activities on demand, merging them with the live window for display and the composer context meter.MessagesTimelinerenders a 'Load older history' header control or a loading indicator, and auto-triggers a single load when the viewport reaches the top, rearming only when the cursor advances or the user scrolls away.Macroscope summarized de8fd65.