feat(chat): adopt order=latest for transcript initial load and paging (issue #859) (#870)

Backend now supports order=latest on GET /api/sessions/{id}/messages
(offset measured back from the newest message, page returned
chronologically). The mobile initial load previously computed its anchor
from a possibly-stale session-list message_count, which landed the first
page at the wrong position (dropped history or a duplicated tail).

- HermesApiService.getSessionMessages: add @Query("order")
- Initial load requests the newest page directly (order=latest, offset=0)
  and drops the fetchServerMessageCount offset math; legacy backends
  (no pagination echo) fall back to the count-based anchor
- Backward paging advances the from-end offset by a full page; a short
  page marks the oldest boundary and stops pagination
- ChatMessage stable keys under latest paging use the server's
  AUTOINCREMENT row id (from-end positions shift as the transcript
  grows and would collide across hydrations)
- syncCurrentSession refetches the newest page and merges by row id
  with a logical-content fallback for re-keyed rows

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-10 14:40:20 +03:00
1 parent ea8fead087
commit 4a034684bb
4 files changed
+392 -32

No files matched your search

@@ -9,10 +9,29 @@ data class SessionMessagesResponse(
val messages: List<SessionMessage>,
val offset: Int? = null,
val total: Int? = null,
val pagination: PaginationInfo? = null,
)
/**
* Echo of the backend's pagination state for GET /api/sessions/{id}/messages
* (hermes_cli/web_routers/sessions.py). Absent on legacy backends that
* predate the `order` param — its presence also proves `order=latest` was
* honored (issue #859).
*/
@Serializable
data class PaginationInfo(
val limit: Int? = null,
val offset: Int? = null,
val order: String? = null,
val returned: Int? = null,
)
@Serializable
data class SessionMessage(
// The gateway's AUTOINCREMENT row id (hermes_state_common.py
// messages.id) — stable, unique and never reused. Used as the chat-list
// stable key under newest-anchored paging (issue #859).
val id: Int? = null,
val role: String? = null,
val content: JsonElement? = null,
val timestamp: JsonElement? = null,