fix(tui): Old messages disappearing during long sessions#26861
Open
vpetrigo wants to merge 15 commits into
Open
fix(tui): Old messages disappearing during long sessions#26861vpetrigo wants to merge 15 commits into
vpetrigo wants to merge 15 commits into
Conversation
15de57d to
11465c2
Compare
Long sessions previously dropped older messages from the in-memory store: the initial sync was capped at 100 and a per-event eviction removed the oldest message whenever new ones arrived, leaving the chat starting mid-conversation and the Timeline / Jump-to-Message features unable to reach the missing entries. - Remove the 100-message limit from the initial session sync so all historical messages are loaded. - Remove the eviction block from the message.updated handler so older messages are no longer purged when new ones arrive. - Add Locale.datetimeFull (DD/MM/YYYY HH:MM) and use it for the Timeline footer so each entry shows full date and time instead of just time.
The v1 messages endpoint previously only supported backward pagination via the `before` cursor. Adding an `after` cursor enables the TUI to recover messages that have been evicted from the in-memory window when the user scrolls back toward the live tail. - MessageV2.page accepts an optional `after` cursor (mutually exclusive with `before`); when set, it returns the next page in ascending chronological order using a `newer()` predicate. - Handler validates that only one of `before`/`after` is supplied, decodes the cursor, and emits the `Link` / `X-Next-Cursor` headers with the correct direction parameter. - MessagesQuery schema gains an optional `after` field. - Tests cover forward pagination across multiple pages and the before+after-together rejection path.
Replaces the unbounded "load all messages on session sync" path with
a windowed loader that keeps memory bounded for long sessions while
still letting the user reach any message via scrolling or the
Timeline.
sync.tsx (TUI sync context):
- Initial sync now fetches the most-recent 100 messages and captures
the `X-Next-Cursor` response header into `messageOlderCursor`.
- New helpers:
- loadOlderMessages: fetches a 50-message page using the older
cursor and prepends.
- loadNewerMessages: fetches a 50-message page using the newer
cursor and appends. Used after eviction to recover the live tail.
- trimNewerMessages / trimOlderMessages: cap the in-memory window
by dropping from the tail/head and recording a cursor for
re-fetch. Eviction skips assistant messages still streaming
(`time.completed` unset) so live output is never lost mid-turn.
- loadAllMessages: paginate exhaustively in both directions
(consumed by the Timeline dialog).
- Live event handling honours the eviction state:
- `message.updated` for an ID past the windowed tail is dropped
when `messageNewerCursor` is set; insertions for IDs already in
the window still update normally.
- `message.part.updated` no longer creates orphan entries for
evicted messages.
SDK v2 client:
- SessionMessagesData and OpencodeClient.messages now accept the new
`after` query parameter.
- openapi.json mirrors the schema change.
Wires the new pagination helpers into the chat surface: - maybeLoadOlderMessages / maybeLoadNewerMessages run after every scroll input (key bindings: page/half-page/line up & down, first & last; mouse wheel via onMouseScroll). They fire only when the viewport is within five rows of the corresponding edge and a cursor is available. - After a successful prepend, the view restores the previous logical scroll position by adjusting for the height delta so the user does not jump. - When the in-memory window grows past 200 messages and the user is far from the opposite edge (>4 viewports away), the matching trim helper evicts the now-distant side; the streaming guard inside trimNewerMessages prevents dropping an in-flight assistant message. - A loader spinner is shown at the top of the scrollbox while older messages are being fetched, and at the bottom while newer ones are being recovered after eviction. Timeline dialog kicks off `loadAllMessages` on mount so every prompt in the session becomes selectable, even ones that were never within the live window.
- Changed all 3 occurrences of "X-Next-Cursor" "x-next-cursor" for case-insensitive header lookup reliability - Fix scrolling in the `maybeLoadOlderMessages` and `maybeLoadNewerMessages`
6 tasks
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.
Issue for this PR
Fix #7380
Type of change
What does this PR do?
• Scrolling up: When user reaches within 5px of the top, loads next 50 older messages via before cursor
• Scrolling down: When user reaches within 5px of the bottom, loads next 50 newer messages via after cursor
DD/MM/YYYY HH:MMtimestamp format in footerIf you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!
How did you verify your code works?
Screenshots / recordings
Original behavior
Updated behavior
Checklist
If you do not follow this template your PR will be automatically rejected.