feat(app): sync session list ordering and split the session repository #344

Merged
rwachters merged 3 commits from feat/app-session-list-ordering-and-repository-split into master 2026-10-12 19:12:33 +00:00
rwachters commented 2026-10-12 19:10:44 +00:00 (Migrated from github.com)

What this does

Keeps the chat session list panel ordered by ChatSessionSummary.updatedAt (most recently active first) and makes it reorder live as sessions receive activity from the running client, then reworks the repository that owns that state.

The list already sorted by updatedAt descending, but the cached summary list was only refreshed by a full load at startup or retry, so turn activity never moved a row.

Commits

  • feat(app): order the session list by last activity — the ordering mechanism and its tests.
  • refactor(app): split session repository in two — the architectural rework.
  • refactor(session-list): drop post-turn refresh — removes the refresh once its value was shown to be marginal.

Session list ordering

  • Every event that persisted a transcript message (UserMessageSaved, AssistantMessageStart, AssistantMessageEnd, and the non-streaming equivalents) raises the cached summary's updatedAt to the persisted message's server timestamp, with a never-backwards guard, so the panel re-sorts instantly with no client-clock timestamps.
  • Covers foreground and spawned-agent/operator turns, which share the same repository instance.
  • Repositioning is instant (no placement animation); selection, per-session indicators, group collapse state and open dialogs survive reorders because the list state never leaves DataState.Success.

Repository split

  • SessionRepository (list concerns) and the new ConversationRepository (transcript concerns) replace the single 989-line DefaultSessionRepository, with DefaultSessionListRepository and DefaultConversationRepository implementing them.
  • Cached transcripts, tool calls and summaries move into SessionDetailsStore, ToolCallStore and SessionSummaryStore; transcript event handling and the summary snapshot merge each get a single owner.
  • Callers depend only on the half they use; collaborators are bound once in the Koin modules.

Behaviour changes to be aware of

  • Ordering now converges on the server order at the next full load rather than after every turn; the post-turn refresh was removed after analysis showed its merge could never add or remove a session and could only raise a held row's updatedAt or overwrite name/groupId (the latter a hazard, not a benefit).
  • Cached transcripts and tool calls are now bounded at 40 sessions (previously 10).
  • A session deleted server-side by another client still lingers in the panel until the next full load, and opening it yields the retryable "error loading session" notification. This is deliberate: the client treats its own create/delete operations as authoritative for row membership.

Testing

  • ./gradlew :app:desktopTestClasses :app:desktopTest — 1103 tests, 0 failures, 2 pre-existing skips.
  • ./gradlew :app:desktopMainClasses, ./gradlew :app:compileKotlinWasmJs — successful.
  • Manual desktop pass: load the list, open a session, send a message (the row rises and stays at the top), rename, create and delete sessions, and confirm a background turn repositions its session.
  • :app:compileAndroidMain could not be run in this environment (Android SDK unavailable); the change touches no androidMain file and no expect/actual.

Out of scope

  • No common/, server/ or worker/ change, no database migration, no wire-model or API change.
  • A pre-existing auto-approve race (the client-only approval gate can read unfetched tool approval preferences) and its stale KDoc in SendMessageUseCase are unrelated to this branch and left for a separate change.
## What this does Keeps the chat session list panel ordered by `ChatSessionSummary.updatedAt` (most recently active first) and makes it reorder live as sessions receive activity from the running client, then reworks the repository that owns that state. The list already sorted by `updatedAt` descending, but the cached summary list was only refreshed by a full load at startup or retry, so turn activity never moved a row. ## Commits - `feat(app): order the session list by last activity` — the ordering mechanism and its tests. - `refactor(app): split session repository in two` — the architectural rework. - `refactor(session-list): drop post-turn refresh` — removes the refresh once its value was shown to be marginal. ## Session list ordering - Every event that persisted a transcript message (`UserMessageSaved`, `AssistantMessageStart`, `AssistantMessageEnd`, and the non-streaming equivalents) raises the cached summary's `updatedAt` to the persisted message's server timestamp, with a never-backwards guard, so the panel re-sorts instantly with no client-clock timestamps. - Covers foreground and spawned-agent/operator turns, which share the same repository instance. - Repositioning is instant (no placement animation); selection, per-session indicators, group collapse state and open dialogs survive reorders because the list state never leaves `DataState.Success`. ## Repository split - `SessionRepository` (list concerns) and the new `ConversationRepository` (transcript concerns) replace the single 989-line `DefaultSessionRepository`, with `DefaultSessionListRepository` and `DefaultConversationRepository` implementing them. - Cached transcripts, tool calls and summaries move into `SessionDetailsStore`, `ToolCallStore` and `SessionSummaryStore`; transcript event handling and the summary snapshot merge each get a single owner. - Callers depend only on the half they use; collaborators are bound once in the Koin modules. ## Behaviour changes to be aware of - Ordering now converges on the server order at the next full load rather than after every turn; the post-turn refresh was removed after analysis showed its merge could never add or remove a session and could only raise a held row's `updatedAt` or overwrite `name`/`groupId` (the latter a hazard, not a benefit). - Cached transcripts and tool calls are now bounded at 40 sessions (previously 10). - A session deleted server-side by another client still lingers in the panel until the next full load, and opening it yields the retryable "error loading session" notification. This is deliberate: the client treats its own create/delete operations as authoritative for row membership. ## Testing - `./gradlew :app:desktopTestClasses :app:desktopTest` — 1103 tests, 0 failures, 2 pre-existing skips. - `./gradlew :app:desktopMainClasses`, `./gradlew :app:compileKotlinWasmJs` — successful. - Manual desktop pass: load the list, open a session, send a message (the row rises and stays at the top), rename, create and delete sessions, and confirm a background turn repositions its session. - `:app:compileAndroidMain` could not be run in this environment (Android SDK unavailable); the change touches no `androidMain` file and no `expect`/`actual`. ## Out of scope - No `common/`, `server/` or `worker/` change, no database migration, no wire-model or API change. - A pre-existing auto-approve race (the client-only approval gate can read unfetched tool approval preferences) and its stale KDoc in `SendMessageUseCase` are unrelated to this branch and left for a separate change.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Torvian.eu/chatbot!344
No description provided.