Repository navigation
Conversation
The TUI keeps the newest 100 messages of a session loaded and drops the
oldest one for every new message past that, silently. Scrolling to the
top of a long session lands somewhere in the middle of the work, the
first prompt is the first thing to go, and nothing brings older
messages back.
Wherever messages are hidden the transcript now shows a divider: a
horizontal rule with the hidden count centered on it, and below it
"load 50 above", "load 50 below" and "load all" (just "load all N" for
a gap of 50 or fewer). The actions are clickable and are palette
commands ("Load 50 hidden messages above/below the divider", "Load all
hidden messages", /load-hidden) with keybinds messages_hidden_above,
messages_hidden_below and messages_hidden_all, unbound by default.
"Above" fills the gap from its older edge, "below" from its newer
edge. Loading grows the window by what it loaded so new output doesn't
immediately drop it again, and "load all" stops dropping messages for
that session. When the view is scrolled away from the bottom, the block
at the top of the viewport is put back on the same row after a load.
tui.json gains transcript.max_messages (default 100, the old constant)
and transcript.keep_first_prompt (default true), which keeps the
session's first prompt above the divider when older messages are
hidden.
The sync store tracks each session's gap as head (messages before it),
count (hidden) and limit (newest messages kept). A new message past the
limit hides the oldest message after the head, and an update to a
message inside the gap is ignored instead of reinserted.
GET /session/:id/message pages backwards only, so it gains after (a
cursor or message id; newer messages, oldest first), order=asc (page
from the oldest message), message ids for before as well as cursors,
and X-Total-Count on paged responses. The TUI anchors on message ids it
already holds, because a cursor goes stale once the message it points
at has been dropped. The first prompt is requested only when the
newest page doesn't cover the whole session.
…load all The hidden-messages divider was a rule with the count and one row of "load 50 above", "load 50 below" and "load all". It is now three rows: "load 50 above load 100 above" above the rule, the rule itself carrying "N messages hidden: load all", and "load 50 below load 100 below" under it. A page is offered only while it is smaller than the gap, so a gap of 51-100 shows only the 50 pages and a gap of 50 or fewer shows only the rule. When the count and "load all" do not fit on the rule together, the rule drops to "N hidden" and "load all" moves to its own centered row under it. While a load is in flight its label reads "loading…" and every action ignores clicks. "load all" now says what it costs. Loading every hidden message of ten real sessions of 2,900 to 13,300 messages took a median 9.3 ms per message before the transcript showed and grew TUI memory by a median 0.52 MB per message (1.3 to 4.3 GB per session). The label is computed per gap from 10 ms and 0.5 MB per message: "(slow)" once the estimate passes 3 s, "(slow / memory heavy)" once it also passes 250 MB, about a third of an idle TUI. That is 300 and 500 hidden messages. The palette command title carries the same note. The divider moves to routes/session/hidden-divider.tsx with a pure layout and takes its width as a prop, so its frames are tested at 40, 80 and 160 columns. sync.session.load takes a page size; new commands session.hidden.above.100 and session.hidden.below.100 with keybinds messages_hidden_above_100 and messages_hidden_below_100, unbound by default.
Contributor
|
The following comment was made by an LLM, it may be inaccurate: |
Nowaker
added a commit
to Nowaker/opencode
that referenced
this pull request
Oct 17, 2026
AI-Tool: opencode 1.19.0-vt-322-dfb56f76c7 AI-Model: anthropic/claude-opus-5-5 AI-Variant: high AI-Platform: linux AI-Harness: Vibeterm 77eca6db AI-Session-ID: ses_eec5b71ddffe4A47jCysfkQh6l
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note: I'm aware v1 is now the maintenance branch and new features go to v2 only. This PR is here for others to use, and I'll submit the v2 version once I've migrated my whole plugin system to it.
Issue for this PR
Closes #56279
The v1 version of #53660 (closed; it targeted
dev). Two commits: the first is #53660's change rebased ontov1, the second the three-row bar and the costedload all.Type of change
What does this PR do?
The TUI keeps a session's newest 100 messages loaded and silently drops the oldest one for every new message past that. The limit counts messages only, and nothing else limits what the transcript renders. This PR makes the hidden part visible and loadable, and the limit configurable.
Bar. Wherever messages are hidden, the transcript shows three rows:
load alldon't fit on the rule, the rule showsN hiddenandload allgets its own row under it.loading…and the other actions ignore clicks.load allstops dropping messages for that session. A scrolled-up reader keeps their place./load-hidden), with keybindsmessages_hidden_above,messages_hidden_above_100,messages_hidden_below,messages_hidden_below_100,messages_hidden_all, unbound by default.load alllabel. Computed per gap from a measured 10 ms and 0.5 MB per hidden message:load allbelow 300 hidden,load all (slow)at 300-499 (an estimated freeze of 3 s or more),load all (slow / memory heavy)from 500 (an estimated 250 MB or more, about a third of an idle TUI). The palette title carries the same note.Settings (
tui.json):transcript.max_messages- newest messages kept loaded, default100(the old constant).transcript.keep_first_prompt- keep the session's first prompt above the bar when older messages are hidden, defaulttrue. This changes what a long session looks like today; happy to flip the default tofalse.API.
GET /session/:id/messageonly paged backwards, so it gainsafter(a cursor or message ID; newer messages, oldest first),order=asc(page from the oldest message), message IDs forbeforeas well as cursors, andX-Total-Count. Conflicting combinations are 400; unpaged requests are unchanged. The TUI anchors on message IDs it already has, because a cursor goes stale once its message is dropped, and asks for the first prompt only when the newest page doesn't cover the session.Memory and time of load all
Ten real sessions of 2,900-13,300 messages, copied into a scratch database, opened in an isolated TUI (160x50, scratch
XDG_*), one run at a time on a busy 18-core Linux desktop. RSS is the TUI process once idle. "Half" istranscript.max_messages: 50against the default100. "Transcript shown" runs from pressing load all until the bar leaves the screen; "back to idle" until a 2 s window uses under 15% of a core.How did you verify your code works?
packages/tui:bun typecheck;bun test.test/routes/session/hidden-divider.test.tsxrenders the bar at 40, 80 and 160 columns, the 51-100 and single-message gaps, the label thresholds, clicks and the loading state.test/cli/cmd/tui/sync-hidden-messages.test.tsxcovers the pinned first prompt,keep_first_prompt: false, an uncropped session making one request, 50- and 100-message pages from both edges, live messages hiding the oldest recent message but never the pinned prompt, and load all.packages/opencode:test/session/messages-pagination.test.tsandtest/server/session-messages.test.ts(forward pages, message-ID anchors,X-Total-Count,Link, the 400s).2,811 messages hidden: load all (slow / memory heavy); clickingload 100 below->2,711 messages hidden.Screenshots / recordings
Checklist
This PR is AI generated, but under direct supervision and on request of @Nowaker.